From b9586c7fee03ff2e7c4f6809cfde6a02986ae2b8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 9 May 2021 18:52:27 -0400 Subject: [PATCH] The great stylecop update Beware those that read into this commit. Here there be style conformance... Closes #1252 --- .editorconfig | 5 + build/analyzers.ruleset | 21 +- build/stylecop.json | 30 + src/Tgstation.Server.Api/ApiHeaders.cs | 88 +- .../DefaultCredentials.cs | 6 +- src/Tgstation.Server.Api/HeaderTypes.cs | 2 +- src/Tgstation.Server.Api/HeadersException.cs | 10 +- .../Models/ChatChannel.cs | 14 +- .../Models/ChatProvider.cs | 8 +- .../Models/ConfigurationType.cs | 10 +- .../Models/DiscordConnectionStringBuilder.cs | 15 +- .../Models/DiscordDMOutputDisplayType.cs | 4 +- .../Models/DreamDaemonSecurity.cs | 12 +- src/Tgstation.Server.Api/Models/EntityId.cs | 2 +- src/Tgstation.Server.Api/Models/ErrorCode.cs | 2 +- .../Models/ErrorCodeExtensions.cs | 2 +- .../Models/FieldPresence.cs | 4 +- .../Models/IConfigurationFile.cs | 6 +- src/Tgstation.Server.Api/Models/Instance.cs | 12 +- .../Models/Internal/ChatBotApiBase.cs | 8 +- .../Models/Internal/ChatBotSettings.cs | 10 +- .../Internal/ChatConnectionStringBuilder.cs | 10 +- .../Models/Internal/CompileJob.cs | 8 +- .../Models/Internal/DreamDaemonApiBase.cs | 6 +- .../Internal/DreamDaemonLaunchParameters.cs | 12 +- .../Models/Internal/DreamDaemonSettings.cs | 4 +- .../Models/Internal/DreamMakerSettings.cs | 8 +- .../Models/Internal/IGitRemoteInformation.cs | 6 +- .../Models/Internal/InstancePermissionSet.cs | 21 +- .../Models/Internal/Job.cs | 19 +- .../Models/Internal/RepositoryApiBase.cs | 2 +- .../Models/Internal/RepositorySettings.cs | 14 +- .../Models/Internal/RevisionInformation.cs | 4 +- .../Models/Internal/ServerInformationBase.cs | 2 +- .../Models/Internal/ServerUpdateRequest.cs | 2 +- .../Models/Internal/SwarmServer.cs | 2 +- .../Models/Internal/TestMergeApiBase.cs | 8 +- .../Models/Internal/TestMergeModelBase.cs | 14 +- .../Models/Internal/UserApiBase.cs | 2 +- .../Models/Internal/UserGroup.cs | 4 +- .../Models/Internal/UserModelBase.cs | 8 +- .../Models/IrcConnectionStringBuilder.cs | 29 +- .../Models/IrcPasswordType.cs | 4 +- src/Tgstation.Server.Api/Models/Limits.cs | 4 +- .../Models/NamedEntity.cs | 2 +- .../Models/OAuthConnection.cs | 3 +- .../Models/OAuthProvider.cs | 4 +- .../Models/OAuthProviderInfo.cs | 2 +- .../Models/PermissionSet.cs | 3 +- .../Models/RemoteGitProvider.cs | 2 +- .../Models/Request/ByondVersionRequest.cs | 2 +- .../Models/Request/ChatBotCreateRequest.cs | 2 +- .../Models/Request/ChatBotUpdateRequest.cs | 2 +- .../Request/ConfigurationFileRequest.cs | 2 +- .../Models/Request/DreamDaemonRequest.cs | 2 +- .../Models/Request/DreamMakerRequest.cs | 2 +- .../Models/Request/InstanceCreateRequest.cs | 2 +- .../Request/InstancePermissionSetRequest.cs | 2 +- .../Models/Request/InstanceUpdateRequest.cs | 2 +- .../Models/Request/RepositoryCreateRequest.cs | 3 +- .../Models/Request/RepositoryUpdateRequest.cs | 7 +- .../Models/Request/UserCreateRequest.cs | 4 +- .../Models/Request/UserGroupCreateRequest.cs | 2 +- .../Models/Request/UserGroupUpdateRequest.cs | 2 +- .../Models/Request/UserUpdateRequest.cs | 3 +- .../Models/RequestOptionsAttribute.cs | 4 +- .../Models/Response/AdministrationResponse.cs | 8 +- .../Models/Response/ByondInstallResponse.cs | 2 +- .../Models/Response/ByondResponse.cs | 2 +- .../Models/Response/ChatBotResponse.cs | 2 +- .../Models/Response/CompileJobResponse.cs | 2 +- .../Response/ConfigurationFileResponse.cs | 6 +- .../Models/Response/DreamDaemonResponse.cs | 11 +- .../Models/Response/DreamMakerResponse.cs | 2 +- .../Models/Response/ErrorMessageResponse.cs | 16 +- .../Models/Response/FileTicketResponse.cs | 2 +- .../Response/InstancePermissionSetResponse.cs | 2 +- .../Models/Response/InstanceResponse.cs | 2 +- .../Models/Response/JobResponse.cs | 10 +- .../Models/Response/LogFileResponse.cs | 2 +- .../Models/Response/PaginatedResponse.cs | 2 +- .../Models/Response/RepositoryResponse.cs | 5 +- .../Response/ServerInformationResponse.cs | 6 +- .../Models/Response/ServerUpdateResponse.cs | 2 +- .../Models/Response/SwarmServerResponse.cs | 2 +- .../Models/Response/TokenResponse.cs | 8 +- .../Models/Response/UserGroupResponse.cs | 5 +- .../Models/Response/UserResponse.cs | 5 +- .../Models/ResponseOptionsAttribute.cs | 2 +- .../Models/RevisionInformation.cs | 8 +- .../Models/ServerUpdate.cs | 2 +- src/Tgstation.Server.Api/Models/TestMerge.cs | 2 +- .../Models/TestMergeParameters.cs | 8 +- src/Tgstation.Server.Api/Models/UserName.cs | 16 +- .../Models/WatchdogStatus.cs | 2 +- .../Properties/ApiVersionAttribute.cs | 4 +- .../Rights/AdministrationRights.cs | 2 +- .../Rights/ByondRights.cs | 2 +- .../Rights/ChatBotRights.cs | 2 +- .../Rights/ConfigurationRights.cs | 4 +- .../Rights/DreamDaemonRights.cs | 2 +- .../Rights/DreamMakerRights.cs | 2 +- .../Rights/InstanceManagerRights.cs | 4 +- .../Rights/InstancePermissionSetRights.cs | 6 +- .../Rights/RepositoryRights.cs | 4 +- .../Rights/RightsHelper.cs | 46 +- src/Tgstation.Server.Api/Rights/RightsType.cs | 6 +- src/Tgstation.Server.Api/Routes.cs | 41 +- .../Tgstation.Server.Api.csproj | 4 + src/Tgstation.Server.Api/VersionExtensions.cs | 2 +- .../AdministrationClient.cs | 8 +- src/Tgstation.Server.Client/ApiClient.cs | 353 ++--- .../ApiClientFactory.cs | 3 +- .../ApiConflictException.cs | 36 +- src/Tgstation.Server.Client/ApiException.cs | 13 +- .../CachedResponseStream.cs | 44 +- .../ClientException.cs | 32 +- .../Components/ByondClient.cs | 9 +- .../Components/ChatBotsClient.cs | 9 +- .../Components/ConfigurationClient.cs | 9 +- .../Components/DreamDaemonClient.cs | 13 +- .../Components/DreamMakerClient.cs | 9 +- .../Components/IByondClient.cs | 23 +- .../Components/IChatBotsClient.cs | 17 +- .../Components/IConfigurationClient.cs | 31 +- .../Components/IDreamDaemonClient.cs | 35 +- .../Components/IDreamMakerClient.cs | 27 +- .../Components/IInstanceClient.cs | 18 +- .../IInstancePermissionSetClient.cs | 33 +- .../Components/IJobsClient.cs | 33 +- .../Components/IRepositoryClient.cs | 15 +- .../Components/InstanceClient.cs | 7 +- .../Components/InstancePermissionSetClient.cs | 9 +- .../Components/JobsClient.cs | 9 +- .../Components/RepositoryClient.cs | 13 +- .../ConflictException.cs | 34 +- .../HttpClientImplementation.cs | 6 +- .../IAdministrationClient.cs | 25 +- src/Tgstation.Server.Client/IApiClient.cs | 197 +-- .../IApiClientFactory.cs | 13 +- src/Tgstation.Server.Client/IHttpClient.cs | 14 +- .../IInstanceManagerClient.cs | 45 +- src/Tgstation.Server.Client/IRequestLogger.cs | 20 +- src/Tgstation.Server.Client/IServerClient.cs | 27 +- .../IServerClientFactory.cs | 27 +- .../IUserGroupsClient.cs | 3 +- src/Tgstation.Server.Client/IUsersClient.cs | 37 +- .../InstanceManagerClient.cs | 8 +- .../InsufficientPermissionsException.cs | 33 +- .../MethodNotSupportedException.cs | 34 +- .../PaginatedClient.cs | 11 +- .../PaginationSettings.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../RateLimitException.cs | 32 +- .../RequestTimeoutException.cs | 33 +- src/Tgstation.Server.Client/ServerClient.cs | 13 +- .../ServerClientFactory.cs | 11 +- .../ServerErrorException.cs | 33 +- .../ServiceUnavailableException.cs | 33 +- .../Tgstation.Server.Client.csproj | 4 + .../UnauthorizedException.cs | 34 +- .../UnrecognizedResponseException.cs | 31 +- .../UserGroupsClient.cs | 5 +- src/Tgstation.Server.Client/UsersClient.cs | 5 +- .../VersionMismatchException.cs | 31 +- src/Tgstation.Server.Host.Console/Program.cs | 16 +- .../GlobalSuppressions.cs | Bin 1316 -> 649 bytes src/Tgstation.Server.Host.Service/Program.cs | 36 +- .../Properties/AssemblyInfo.cs | 2 +- .../ServerService.cs | 84 +- .../Tgstation.Server.Host.Service.csproj | 4 + .../IWatchdog.cs | 12 +- .../IWatchdogFactory.cs | 8 +- .../Tgstation.Server.Host.Watchdog.csproj | 4 + .../Watchdog.cs | 92 +- .../WatchdogFactory.cs | 5 +- .../Components/Byond/ByondExecutableLock.cs | 17 +- .../Components/Byond/ByondInstallerBase.cs | 8 +- .../Components/Byond/ByondManager.cs | 278 ++-- .../Components/Byond/IByondExecutableLock.cs | 10 +- .../Components/Byond/IByondInstaller.cs | 32 +- .../Components/Byond/IByondManager.cs | 27 +- .../Components/Byond/PosixByondInstaller.cs | 25 +- .../Components/Byond/WindowsByondInstaller.cs | 44 +- .../Components/Chat/ChannelMapping.cs | 8 +- .../Components/Chat/ChannelRepresentation.cs | 17 +- .../Components/Chat/ChatManager.cs | 762 +++++------ .../Components/Chat/ChatManagerFactory.cs | 24 +- .../Components/Chat/ChatTrackingContext.cs | 10 +- .../Components/Chat/ChatUser.cs | 17 +- .../Components/Chat/Commands/ByondCommand.cs | 13 +- .../Chat/Commands/CommandFactory.cs | 33 +- .../Components/Chat/Commands/CustomCommand.cs | 8 +- .../Components/Chat/Commands/ICommand.cs | 20 +- .../Chat/Commands/ICommandFactory.cs | 6 +- .../Components/Chat/Commands/KekCommand.cs | 4 +- .../Chat/Commands/PullRequestsCommand.cs | 32 +- .../Chat/Commands/RevisionCommand.cs | 13 +- .../Chat/Commands/VersionCommand.cs | 9 +- .../Components/Chat/IChatManager.cs | 38 +- .../Components/Chat/IChatManagerFactory.cs | 13 +- .../Components/Chat/IChatTrackingContext.cs | 5 +- .../Components/Chat/ICustomCommandHandler.cs | 16 +- .../Components/Chat/Message.cs | 8 +- .../Chat/Providers/DiscordProvider.cs | 698 +++++----- .../Components/Chat/Providers/IProvider.cs | 35 +- .../Chat/Providers/IProviderFactory.cs | 10 +- .../Components/Chat/Providers/IrcProvider.cs | 558 ++++---- .../Components/Chat/Providers/Provider.cs | 138 +- .../Chat/Providers/ProviderFactory.cs | 20 +- .../Components/Deployment/DmbFactory.cs | 110 +- .../Components/Deployment/DmbProvider.cs | 19 +- .../Components/Deployment/DreamMaker.cs | 775 +++++------ .../Components/Deployment/ICompileJobSink.cs | 13 +- .../Components/Deployment/IDmbFactory.cs | 32 +- .../Components/Deployment/IDmbProvider.cs | 11 +- .../Components/Deployment/IDreamMaker.cs | 5 +- .../Remote/BaseRemoteDeploymentManager.cs | 110 +- .../Remote/GitHubRemoteDeploymentManager.cs | 134 +- .../Remote/GitLabRemoteDeploymentManager.cs | 125 +- .../Remote/IRemoteDeploymentManager.cs | 3 +- .../Remote/IRemoteDeploymentManagerFactory.cs | 2 +- .../Remote/NoOpRemoteDeploymentManager.cs | 3 +- .../Remote/RemoteDeploymentManagerFactory.cs | 6 +- .../Deployment/SwappableDmbProvider.cs | 3 +- .../Deployment/TemporaryDmbProvider.cs | 17 +- .../Components/Events/EventConsumer.cs | 15 +- .../Components/Events/EventScriptAttribute.cs | 2 +- .../Components/Events/EventType.cs | 4 +- .../Components/Events/IEventConsumer.cs | 2 +- .../Components/IInstance.cs | 5 +- .../Components/IInstanceCore.cs | 21 +- .../Components/IInstanceCoreProvider.cs | 2 +- .../Components/IInstanceFactory.cs | 14 +- .../Components/IInstanceManager.cs | 33 +- .../Components/IRenameNotifyee.cs | 2 +- .../Components/Instance.cs | 664 +++++----- .../Components/InstanceContainer.cs | 4 +- .../Components/InstanceFactory.cs | 74 +- .../Components/InstanceManager.cs | 94 +- .../Components/InstanceWrapper.cs | 3 +- .../Interop/Bridge/BridgeCommandType.cs | 2 +- .../Interop/Bridge/BridgeParameters.cs | 1 + .../Interop/Bridge/BridgeRegistration.cs | 4 +- .../Interop/Bridge/IBridgeDispatcher.cs | 2 +- .../Interop/Bridge/IBridgeHandler.cs | 2 +- .../Interop/Bridge/IBridgeRegistration.cs | 2 +- .../Interop/Bridge/RuntimeInformation.cs | 15 +- .../Interop/Bridge/TestMergeInformation.cs | 15 +- .../Components/Interop/ChatMessage.cs | 2 +- .../Components/Interop/ChatUpdate.cs | 3 +- .../Components/Interop/DMApiConstants.cs | 12 +- .../Components/Interop/DMApiParameters.cs | 2 +- .../Components/Interop/Topic/ChatCommand.cs | 11 +- .../Interop/Topic/EventNotification.cs | 7 +- .../Interop/Topic/TopicCommandType.cs | 4 +- .../Interop/Topic/TopicParameters.cs | 37 +- .../Components/Interop/Topic/TopicResponse.cs | 1 + .../Repository/DefaultGitRemoteFeatures.cs | 3 +- .../Repository/GitHubRemoteFeatures.cs | 12 +- .../Repository/GitLabRemoteFeatures.cs | 14 +- .../Repository/GitRemoteFeaturesBase.cs | 10 +- .../Repository/GitRemoteFeaturesFactory.cs | 6 +- .../Repository/ICredentialsProvider.cs | 12 +- .../IGitRemoteAdditionalInformation.cs | 3 +- .../Repository/IGitRemoteFeatures.cs | 6 +- .../Repository/IGitRemoteFeaturesFactory.cs | 3 +- .../Components/Repository/ILibGit2Commands.cs | 5 +- .../Repository/ILibGit2RepositoryFactory.cs | 5 +- .../Components/Repository/IRepository.cs | 117 +- .../Repository/IRepositoryManager.cs | 32 +- .../Components/Repository/LibGit2Commands.cs | 5 +- .../Repository/LibGit2RepositoryFactory.cs | 44 +- .../Components/Repository/Repository.cs | 870 +++++++------ .../Repository/RepositoryManager.cs | 34 +- .../Components/Session/ApiValidationStatus.cs | 2 +- .../Session/CombinedTopicResponse.cs | 3 +- .../Components/Session/ISessionController.cs | 45 +- .../Session/ISessionControllerFactory.cs | 25 +- .../Components/Session/ISessionPersistor.cs | 10 +- .../Components/Session/ITopicClientFactory.cs | 7 +- .../Components/Session/LaunchResult.cs | 8 +- .../Components/Session/ReattachInformation.cs | 43 +- .../Components/Session/RebootState.cs | 4 +- .../Components/Session/SessionController.cs | 209 +-- .../Session/SessionControllerFactory.cs | 75 +- .../Components/Session/SessionPersistor.cs | 31 +- .../Components/Session/TopicClientFactory.cs | 9 +- .../Components/StaticFiles/Configuration.cs | 164 ++- .../Components/StaticFiles/IConfiguration.cs | 78 +- .../StaticFiles/ServerSideModifications.cs | 16 +- .../Components/Watchdog/BasicWatchdog.cs | 38 +- .../Components/Watchdog/IWatchdog.cs | 52 +- .../Components/Watchdog/IWatchdogFactory.cs | 20 +- .../Components/Watchdog/MonitorAction.cs | 6 +- .../Watchdog/MonitorActivationReason.cs | 4 +- .../Components/Watchdog/PosixWatchdog.cs | 11 +- .../Watchdog/PosixWatchdogFactory.cs | 9 +- .../Components/Watchdog/WatchdogBase.cs | 642 ++++----- .../Components/Watchdog/WatchdogFactory.cs | 26 +- .../Components/Watchdog/WindowsWatchdog.cs | 17 +- .../Watchdog/WindowsWatchdogFactory.cs | 6 +- .../ControlPanelConfiguration.cs | 12 +- .../Configuration/DatabaseConfiguration.cs | 16 +- .../Configuration/DatabaseType.cs | 2 +- .../Configuration/FileLoggingConfiguration.cs | 24 +- .../Configuration/GeneralConfiguration.cs | 32 +- .../Configuration/OAuthConfiguration.cs | 2 +- .../Configuration/OAuthConfigurationBase.cs | 10 +- .../Configuration/SecurityConfiguration.cs | 7 +- .../Configuration/SwarmConfiguration.cs | 3 +- .../Configuration/UpdatesConfiguration.cs | 16 +- .../Controllers/AdministrationController.cs | 75 +- .../Controllers/ApiController.cs | 260 ++-- .../Controllers/BridgeController.cs | 22 +- .../Controllers/ByondController.cs | 26 +- .../Controllers/ChatController.cs | 33 +- .../Controllers/ConfigurationController.cs | 96 +- .../Controllers/ControlPanelController.cs | 10 +- .../Controllers/DreamDaemonController.cs | 169 +-- .../Controllers/DreamMakerController.cs | 62 +- .../Controllers/HomeController.cs | 67 +- .../Controllers/InstanceController.cs | 273 ++-- .../InstancePermissionSetController.cs | 30 +- .../Controllers/InstanceRequiredController.cs | 80 +- .../Controllers/JobController.cs | 26 +- .../Controllers/LimitedFileStreamResult.cs | 11 +- .../LimitedFileStreamResultExecutor.cs | 9 +- .../Controllers/PaginatableResult.cs | 9 +- .../Controllers/RepositoryController.cs | 328 +++-- .../Controllers/SwarmController.cs | 114 +- .../Controllers/TgsAuthorizeAttribute.cs | 48 +- .../Controllers/TransferController.cs | 26 +- .../Controllers/UserController.cs | 108 +- .../Controllers/UserGroupController.cs | 21 +- src/Tgstation.Server.Host/Core/Application.cs | 94 +- .../Core/GitHubClientFactory.cs | 22 +- .../Core/IAsyncDelayer.cs | 10 +- .../Core/IGitHubClientFactory.cs | 10 +- .../Core/IPortAllocator.cs | 2 +- .../Core/IRestartHandler.cs | 10 +- .../Core/IRestartRegistration.cs | 4 +- .../Core/IServerControl.cs | 35 +- .../Core/IServerUpdateInitiator.cs | 2 +- .../Core/OpenApiEnumVarNamesExtension.cs | 9 +- .../Core/PortAllocator.cs | 32 +- .../Core/RestartRegistration.cs | 8 +- .../Core/SemaphoreSlimContext.cs | 20 +- .../Core/ServerPortProivder.cs | 10 +- .../Core/ServerUpdateInitiator.cs | 12 +- .../Core/ServerUpdateResult.cs | 2 +- .../Core/SwaggerConfiguration.cs | 605 ++++----- .../Database/DatabaseCollection.cs | 9 +- .../Database/DatabaseConnectionFactory.cs | 10 +- .../Database/DatabaseContext.cs | 96 +- .../Database/DatabaseContextFactory.cs | 11 +- .../Database/DatabaseSeeder.cs | 179 +-- .../DesignTimeDbContextFactoryHelpers.cs | 11 +- .../Design/MySqlDesignTimeDbContextFactory.cs | 1 + .../PostgresSqlDesignTimeDbContextFactory.cs | 1 + .../SqlServerDesignTimeDbContextFactory.cs | 1 + .../SqliteDesignTimeDbContextFactory.cs | 1 + .../Database/IDatabaseConnectionFactory.cs | 9 +- .../Database/IDatabaseContext.cs | 26 +- .../Database/IDatabaseContextFactory.cs | 8 +- .../20180906135553_MSInitialCreate.cs | 35 +- .../20180906143029_MYInitialCreate.cs | 35 +- .../20180918020726_MYAddMinimumSecurity.cs | 6 +- .../20180918021228_MSAddMinimumSecurity.cs | 6 +- ...18204520_MYNullableAndForeignKeyCleanup.cs | 3 +- ...18205224_MSNullableAndForeignKeyCleanup.cs | 3 +- ...0926033145_MSReattachCompileJobRequired.cs | 7 +- ...0926034014_MYReattachCompileJobRequired.cs | 7 +- ...0181124231534_MSToggleTestmergeComments.cs | 7 +- ...0181124231549_MYToggleTestmergeComments.cs | 7 +- .../20190219041825_MSFixRevInfoIndex.cs | 7 +- .../20190219042440_MYFixRevInfoIndex.cs | 5 +- ...170252_MSAddChatBotReconnectionInterval.cs | 5 +- ...171159_MYAddChatBotReconnectionInterval.cs | 5 +- .../20200411164808_MSTonsOfValidation.cs | 5 +- .../20200411170010_MYTonsOfValidation.cs | 5 +- .../20200420175359_MSLimitsOnChat.cs | 6 +- .../20200420181015_MYLimitsOnChat.cs | 6 +- ...00422001202_MSAddCompileJobDMApiVersion.cs | 5 +- ...00422010115_MYAddCompileJobDMApiVersion.cs | 3 +- ...200423052142_MSKillJsonsAddDMApiVersion.cs | 5 +- ...200423052709_MYKillJsonsAddDMApiVersion.cs | 3 +- .../20200425024433_MSAddJobErrorCodes.cs | 5 +- .../20200425024953_MYAddJobErrorCodes.cs | 5 +- .../20200427173852_MSFixCascadingDelete.cs | 5 +- .../20200427180159_MYFixCascadingDelete.cs | 5 +- .../Migrations/20200427180904_SLRebuild.cs | 31 +- .../20200511005957_MSAddHeartbeat.cs | 5 +- .../20200511011048_MYAddHeartbeat.cs | 5 +- .../20200511012117_SLAddHeartbeat.cs | 7 +- .../20200511162912_SLRemoveSoftColumns.cs | 7 +- .../20200512181801_MSRemoveSoftColumns.cs | 5 +- .../20200512182535_MYRemoveSoftColumns.cs | 5 +- .../Migrations/20200516111712_PGCreate.cs | 31 +- .../20200529171541_MYFixForeignKey.cs | 5 +- .../20200616175424_MSTopicTimeout.cs | 5 +- .../20200616175535_MYTopicTimeout.cs | 5 +- .../20200616180742_SLTopicTimeout.cs | 7 +- .../20200616180842_PGTopicTimeout.cs | 5 +- ...0629184803_MSRemoveExperimentalWatchdog.cs | 7 +- ...0629184843_MYRemoveExperimentalWatchdog.cs | 7 +- ...0629184928_PGRemoveExperimentalWatchdog.cs | 7 +- ...0629185014_SLRemoveExperimentalWatchdog.cs | 11 +- .../20200705163512_MSAllowNullDMApi.cs | 5 +- .../20200705163547_MYAllowNullDMApi.cs | 5 +- .../20200705163624_PGAllowNullDMApi.cs | 5 +- .../20200705163700_SLAllowNullDMApi.cs | 12 +- .../20200807213255_MSAddDeploymentColumns.cs | 3 +- .../20200807213742_MYAddDeploymentColumns.cs | 3 +- .../20200807214330_PGAddDeploymentColumns.cs | 3 +- .../20200807214754_SLAddDeploymentColumns.cs | 7 +- ...01106082502_MSAddAdditionalDDParameters.cs | 3 +- ...01106082556_MYAddAdditionalDDParameters.cs | 3 +- ...01106082653_PGAddAdditionalDDParameters.cs | 3 +- ...01106082741_SLAddAdditionalDDParameters.cs | 5 +- .../20201122231219_MSAddOAuthConnections.cs | 5 +- .../20201122231327_MYAddOAuthConnections.cs | 5 +- .../20201122231443_PGAddOAuthConnections.cs | 5 +- .../20201122231546_SLAddOAuthConnections.cs | 5 +- ...201209194250_MSGenericTestMergingUpdate.cs | 3 +- ...201209194348_MYGenericTestMergingUpdate.cs | 3 +- ...201209194500_PGGenericTestMergingUpdate.cs | 3 +- ...201209194554_SLGenericTestMergingUpdate.cs | 5 +- .../20201214181824_MSAddUserGroups.cs | 11 +- .../20201214181914_MYAddUserGroups.cs | 11 +- .../20201214182008_PGAddUserGroups.cs | 11 +- .../20201214182101_SLAddUserGroups.cs | 15 +- .../20201222175310_MSAddSwarmIdentifer.cs | 3 +- .../20201222175357_MYAddSwarmIdentifer.cs | 3 +- .../20201222175444_PGAddSwarmIdentifer.cs | 3 +- .../20201222175532_SLAddSwarmIdentifer.cs | 5 +- .../20210112154040_MSAddRevInfoTimestamp.cs | 3 +- .../20210112154123_MYAddRevInfoTimestamp.cs | 3 +- .../20210112154203_PGAddRevInfoTimestamp.cs | 3 +- .../20210112154243_SLAddRevInfoTimestamp.cs | 5 +- .../20210211173222_MSTruncateInstanceNames.cs | 3 +- .../20210211173316_MYTruncateInstanceNames.cs | 3 +- .../20210211173407_PGTruncateInstanceNames.cs | 35 +- .../Database/MySqlDatabaseContext.cs | 13 +- .../Database/PostgresSqlDatabaseContext.cs | 13 +- .../Database/SqlServerDatabaseContext.cs | 15 +- .../Database/SqliteDatabaseContext.cs | 13 +- .../ApplicationBuilderExtensions.cs | 47 +- .../Extensions/ChatChannelExtensions.cs | 3 +- .../Extensions/Converters/BoolConverter.cs | 3 +- .../Extensions/Converters/VersionConverter.cs | 5 +- .../DatabaseCollectionExtensions.cs | 8 +- .../Extensions/DateTimeOffsetExtensions.cs | 2 +- .../Extensions/HostBuilderExtensions.cs | 10 +- .../Extensions/ServiceCollectionExtensions.cs | 20 +- .../Extensions/SocketExtensions.cs | 2 +- .../Extensions/TaskExtensions.cs | 2 +- .../Extensions/WebHostBuilderExtensions.cs | 8 +- src/Tgstation.Server.Host/IO/Console.cs | 139 +- .../IO/DefaultIOManager.cs | 290 +++-- src/Tgstation.Server.Host/IO/IConsole.cs | 28 +- src/Tgstation.Server.Host/IO/IIOManager.cs | 180 +-- .../IO/IPostWriteHandler.cs | 6 +- .../IO/ISymlinkFactory.cs | 12 +- .../IO/ISynchronousIOManager.cs | 58 +- .../IO/PosixPostWriteHandler.cs | 11 +- .../IO/PosixSymlinkFactory.cs | 43 +- .../IO/ResolvingIOManager.cs | 12 +- .../IO/SynchronousIOManager.cs | 10 +- .../IO/WindowsPostWriteHandler.cs | 2 +- .../IO/WindowsSymlinkFactory.cs | 55 +- src/Tgstation.Server.Host/IServer.cs | 10 +- src/Tgstation.Server.Host/IServerFactory.cs | 11 +- src/Tgstation.Server.Host/Jobs/IJobManager.cs | 38 +- .../Jobs/JobEntrypoint.cs | 1 + .../Jobs/JobException.cs | 21 +- src/Tgstation.Server.Host/Jobs/JobHandler.cs | 23 +- src/Tgstation.Server.Host/Jobs/JobManager.cs | 232 ++-- src/Tgstation.Server.Host/Models/ChatBot.cs | 11 +- .../Models/ChatChannel.cs | 8 +- .../Models/CompileJob.cs | 12 +- .../Models/DreamDaemonSettings.cs | 6 +- .../Models/DreamMakerSettings.cs | 11 +- .../Models/IApiTransformable.cs | 2 +- src/Tgstation.Server.Host/Models/Instance.cs | 19 +- .../Models/InstancePermissionSet.cs | 9 +- src/Tgstation.Server.Host/Models/Job.cs | 15 +- .../Models/OAuthConnection.cs | 4 +- .../Models/PermissionSet.cs | 2 +- .../Models/ReattachInformation.cs | 6 +- .../Models/ReattachInformationBase.cs | 19 +- .../Models/RepositorySettings.cs | 9 +- .../Models/RevInfoTestMerge.cs | 8 +- .../Models/RevisionInformation.cs | 18 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 12 +- src/Tgstation.Server.Host/Models/User.cs | 23 +- src/Tgstation.Server.Host/Models/UserGroup.cs | 3 +- src/Tgstation.Server.Host/NativeMethods.cs | 41 +- src/Tgstation.Server.Host/Program.cs | 16 +- .../Properties/MasterVersionsAttribute.cs | 6 +- .../Security/AuthenticationContext.cs | 19 +- .../Security/AuthenticationContextFactory.cs | 20 +- .../Security/ClaimsInjector.cs | 28 +- .../Security/CryptographySuite.cs | 14 +- .../Security/IAuthenticationContext.cs | 13 +- .../Security/IAuthenticationContextFactory.cs | 18 +- .../Security/IClaimsInjector.cs | 17 +- .../Security/ICryptographySuite.cs | 28 +- .../Security/IIdentityCache.cs | 17 +- .../Security/ISystemIdentity.cs | 20 +- .../Security/ISystemIdentityFactory.cs | 21 +- .../Security/ITokenFactory.cs | 16 +- .../Security/IdentityCache.cs | 39 +- .../Security/IdentityCacheObject.cs | 24 +- .../Security/OAuth/BaseOAuthValidator.cs | 16 +- .../Security/OAuth/DiscordOAuthValidator.cs | 8 +- .../Security/OAuth/GenericOAuthValidator.cs | 62 +- .../Security/OAuth/GitHubOAuthValidator.cs | 16 +- .../Security/OAuth/IOAuthProviders.cs | 5 +- .../Security/OAuth/IOAuthValidator.cs | 3 +- .../Security/OAuth/KeycloakOAuthValidator.cs | 8 +- .../Security/OAuth/OAuthProviders.cs | 10 +- .../Security/OAuth/OAuthTokenRequest.cs | 7 +- .../Security/OAuth/TGBaseResponse.cs | 2 +- .../Security/OAuth/TGCreateSessionResponse.cs | 2 +- .../Security/OAuth/TGForumsOAuthValidator.cs | 24 +- .../OAuth/TGGetSessionInfoResponse.cs | 2 +- .../Security/PosixSystemIdentityFactory.cs | 3 +- .../Security/TokenFactory.cs | 22 +- .../Security/WindowsSystemIdentity.cs | 37 +- .../Security/WindowsSystemIdentityFactory.cs | 147 ++- src/Tgstation.Server.Host/Server.cs | 104 +- src/Tgstation.Server.Host/ServerFactory.cs | 12 +- .../Setup/IPostSetupServices.cs | 2 +- .../Setup/IPostSetupServices{TLoggerType}.cs | 2 +- .../Setup/PostSetupServices.cs | 6 +- .../Setup/SetupApplication.cs | 8 +- .../Setup/SetupWizard.cs | 161 +-- .../Setup/SetupWizardMode.cs | 4 +- .../Swarm/ISwarmOperations.cs | 3 +- .../Swarm/ISwarmService.cs | 3 +- .../Swarm/SwarmConstants.cs | 2 +- .../Swarm/SwarmRegistrationRequest.cs | 3 +- .../Swarm/SwarmRegistrationResult.cs | 2 +- .../Swarm/SwarmServersUpdateRequest.cs | 3 +- .../Swarm/SwarmService.cs | 1142 +++++++++-------- .../Swarm/SwarmUpdateRequest.cs | 2 +- .../System/AssemblyInformationProvider.cs | 5 +- .../System/IAssemblyInformationProvider.cs | 2 +- .../System/INetworkPromptReaper.cs | 8 +- .../System/IPlatformIdentifier.cs | 6 +- src/Tgstation.Server.Host/System/IProcess.cs | 20 +- .../System/IProcessBase.cs | 6 +- .../System/IProcessExecutor.cs | 18 +- .../System/IProcessFeatures.cs | 2 +- .../System/PlatformIdentifier.cs | 2 +- .../System/PosixProcessFeatures.cs | 10 +- src/Tgstation.Server.Host/System/Process.cs | 69 +- .../System/ProcessExecutor.cs | 30 +- .../System/ProgramShutdownTokenSource.cs | 4 +- .../System/WindowsNetworkPromptReaper.cs | 193 +-- .../System/WindowsProcessFeatures.cs | 6 +- .../Tgstation.Server.Host.csproj | 6 +- .../Transfer/FileDownloadProvider.cs | 5 +- .../Transfer/FileTransferService.cs | 88 +- .../Transfer/FileUploadProvider.cs | 10 +- .../Transfer/IFileTransferStreamHandler.cs | 3 +- .../Transfer/IFileTransferTicketProvider.cs | 2 +- .../Transfer/IFileUploadTicket.cs | 3 +- .../Models/TestErrorCodeExtensions.cs | 2 +- .../Rights/TestRights.cs | 2 +- .../TestApiHeaders.cs | 1 + .../Components/TestDreamDaemonClient.cs | 1 + .../TestApiClient.cs | 1 + .../TestProgram.cs | 1 + .../TestServerService.cs | 3 +- .../Byond/TestPosixByondInstaller.cs | 7 +- .../Chat/Providers/TestDiscordProvider.cs | 3 +- .../Events/TestEventScriptAttribute.cs | 2 +- .../Components/Events/TestEventType.cs | 5 +- .../Core/TestApplication.cs | 1 + .../Core/TestGitHubClientFactory.cs | 1 + .../Database/TestDatabaseConnectionFactory.cs | 3 +- .../Database/TestDatabaseContextFactory.cs | 3 +- .../Converters/TestVersionConverter.cs | 2 + .../IO/TestConsole.cs | 2 +- .../Jobs/TestJobHandler.cs | 2 +- .../Security/TestAuthenticationContext.cs | 1 + .../Setup/TestSetupWizard.cs | 1 + .../System/TestProcessFeatures.cs | 3 +- .../TestProgram.cs | 3 +- .../TestServerFactory.cs | 1 + .../AdministrationTest.cs | 1 + tests/Tgstation.Server.Tests/ApiAssert.cs | 1 + .../Instance/ByondTest.cs | 1 + .../Instance/ChatTest.cs | 3 +- .../Instance/ConfigurationTest.cs | 4 +- .../Instance/DeploymentTest.cs | 1 + .../Instance/InstanceTest.cs | 1 + .../Instance/JobsRequiredTest.cs | 1 + .../Instance/RepositoryTest.cs | 3 +- .../Instance/WatchdogTest.cs | 5 +- .../InstanceManagerTest.cs | 1 + .../Tgstation.Server.Tests/IntegrationTest.cs | 7 +- .../Tgstation.Server.Tests/RawRequestTests.cs | 1 + tests/Tgstation.Server.Tests/TestingServer.cs | 1 + tests/Tgstation.Server.Tests/UsersTest.cs | 1 + tests/Tgstation.Server.Tests/VersionsTest.cs | 3 +- tools/ReleaseNotes/Program.cs | 4 +- 609 files changed, 10032 insertions(+), 8923 deletions(-) diff --git a/.editorconfig b/.editorconfig index d0556da637..5c4a83ecd1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,15 +2,20 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true + [*.cs] +charset = utf-8-bom indent_style = tab indent_size = 4 + [*.csproj] indent_style = space indent_size = 2 + [*.dm] indent_style = tab indent_size = 4 + [*.yml] indent_style = space indent_size = 2 diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset index bbf7b12dab..2699bd4001 100644 --- a/build/analyzers.ruleset +++ b/build/analyzers.ruleset @@ -1,5 +1,5 @@  - + @@ -30,7 +30,6 @@ - @@ -66,7 +65,6 @@ - @@ -1025,31 +1023,22 @@ - - - - - - - - - - + - - + - + + diff --git a/build/stylecop.json b/build/stylecop.json index e683e4e39b..bc19a81992 100644 --- a/build/stylecop.json +++ b/build/stylecop.json @@ -1,8 +1,38 @@ { "settings": { + "documentationRules": { + "documentPrivateElements": true, + "documentPrivateFields": true, + "excludeFromPunctuationCheck": [ + "remarks" + ] + }, "indentation": { "tabSize": 4, "useTabs": true + }, + "layoutRules": { + "newlineAtEndOfFile": "require" + }, + "maintainabilityRules": { + "topLevelTypes": [ + "class", + "delegate", + "enum", + "interface", + "struct" + ] + }, + "orderingRules": { + "blankLinesBetweenUsingGroups": "require", + "elementOrder": [ + "constant", + "kind", + "static", + "readonly", + "accessibility" + ], + "usingDirectivesPlacement": "outsideNamespace" } } } diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 1a62a1d0d9..09bc99bb75 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -1,20 +1,22 @@ -using Microsoft.AspNetCore.Http.Headers; -using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; using System.Net.Mime; using System.Reflection; using System.Text; + +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Properties; namespace Tgstation.Server.Api { /// - /// Represents the header that must be present for every server request + /// Represents the header that must be present for every server request. /// public sealed class ApiHeaders { @@ -34,62 +36,62 @@ namespace Tgstation.Server.Api public const string OAuthProviderHeader = "OAuthProvider"; /// - /// The JWT authentication header scheme + /// The JWT authentication header scheme. /// public const string BearerAuthenticationScheme = "Bearer"; /// - /// The JWT authentication header scheme + /// The JWT authentication header scheme. /// public const string BasicAuthenticationScheme = "Basic"; /// - /// The JWT authentication header scheme + /// The JWT authentication header scheme. /// public const string OAuthAuthenticationScheme = "OAuth"; /// - /// The current - /// - static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName(); - - /// - /// Get the version of the the caller is using + /// Get the version of the the caller is using. /// public static readonly Version Version = Version.Parse(ApiVersionAttribute.Instance.RawApiVersion); /// - /// The instance being accessed + /// The current . + /// + static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName(); + + /// + /// The instance being accessed. /// public long? InstanceId { get; set; } /// - /// The client's user agent as a if valid + /// The client's user agent as a if valid. /// public ProductHeaderValue? UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null; /// - /// The client's raw user agent + /// The client's raw user agent. /// public string? RawUserAgent { get; } /// - /// The client's API version + /// The client's API version. /// public Version ApiVersion { get; } /// - /// The client's JWT + /// The client's JWT. /// public string? Token { get; } /// - /// The client's username + /// The client's username. /// public string? Username { get; } /// - /// The client's password + /// The client's password. /// public string? Password { get; } @@ -104,17 +106,17 @@ namespace Tgstation.Server.Api public bool IsTokenAuthentication => Token != null && !OAuthProvider.HasValue; /// - /// Checks if a given is compatible with our own + /// Checks if a given is compatible with our own. /// - /// The to test - /// if the given version is compatible with the API. otherwise + /// The to test. + /// if the given version is compatible with the API. otherwise. public static bool CheckCompatibility(Version otherVersion) => Version.Major == (otherVersion?.Major ?? throw new ArgumentNullException(nameof(otherVersion))); /// - /// Construct for JWT authentication + /// Initializes a new instance of the class. Used for token authentication. /// - /// The value of - /// The value of + /// The value of . + /// The value of . /// The value of . public ApiHeaders(ProductHeaderValue userAgent, string token, OAuthProvider? oauthProvider = null) : this(userAgent, token, null, null) { @@ -127,11 +129,11 @@ namespace Tgstation.Server.Api } /// - /// Construct for password authentication + /// Initializes a new instance of the class. Used for password authentication. /// - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password) { if (userAgent == null) @@ -143,9 +145,9 @@ namespace Tgstation.Server.Api } /// - /// Construct and validates from a set of + /// Initializes a new instance of the class. /// - /// The containing the + /// The containing the serialized . /// If a missing should be ignored. /// Thrown if the constitue invalid . #pragma warning disable CA1502 @@ -270,12 +272,12 @@ namespace Tgstation.Server.Api #pragma warning restore CA1502 /// - /// Construct + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password) { RawUserAgent = userAgent?.ToString(); @@ -286,16 +288,16 @@ namespace Tgstation.Server.Api } /// - /// Checks if the is compatible with + /// Checks if the is compatible with . /// - /// if the API is compatible, otherwise + /// if the API is compatible, otherwise. public bool Compatible() => CheckCompatibility(ApiVersion); /// - /// Set using the . This initially clears + /// Set using the . This initially clears . /// - /// The to set - /// The instance for the request + /// The to set. + /// The instance for the request. public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null) { if (headers == null) diff --git a/src/Tgstation.Server.Api/DefaultCredentials.cs b/src/Tgstation.Server.Api/DefaultCredentials.cs index d816f24739..0bb468b7a7 100644 --- a/src/Tgstation.Server.Api/DefaultCredentials.cs +++ b/src/Tgstation.Server.Api/DefaultCredentials.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api +namespace Tgstation.Server.Api { /// /// Represents initial credentials used by the server. @@ -6,12 +6,12 @@ namespace Tgstation.Server.Api public static class DefaultCredentials { /// - /// The name of the default admin user + /// The name of the default admin user. /// public static readonly string AdminUserName = "Admin"; /// - /// The default admin password + /// The default admin password. /// public static readonly string DefaultAdminUserPassword = "ISolemlySwearToDeleteTheDataDirectory"; } diff --git a/src/Tgstation.Server.Api/HeaderTypes.cs b/src/Tgstation.Server.Api/HeaderTypes.cs index b45b6b0380..7260426d00 100644 --- a/src/Tgstation.Server.Api/HeaderTypes.cs +++ b/src/Tgstation.Server.Api/HeaderTypes.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api { diff --git a/src/Tgstation.Server.Api/HeadersException.cs b/src/Tgstation.Server.Api/HeadersException.cs index e13ef45598..62bce1433b 100644 --- a/src/Tgstation.Server.Api/HeadersException.cs +++ b/src/Tgstation.Server.Api/HeadersException.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Api public HeaderTypes MissingOrMalformedHeaders { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The error message. @@ -23,14 +23,14 @@ namespace Tgstation.Server.Api } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// public HeadersException() { } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The error message. public HeadersException(string message) : base(message) @@ -38,10 +38,10 @@ namespace Tgstation.Server.Api } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The error message. - /// The inner for the base + /// The inner for the base . public HeadersException(string message, Exception innerException) : base(message, innerException) { } diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 549c9678b4..49dc7c0898 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -1,9 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { /// - /// Indicates a chat channel + /// Indicates a chat channel. /// public class ChatChannel { @@ -16,31 +16,31 @@ namespace Tgstation.Server.Api.Models public string? IrcChannel { get; set; } /// - /// The Discord channel ID + /// The Discord channel ID. /// [ResponseOptions] public ulong? DiscordChannelId { get; set; } /// - /// If the is an admin channel + /// If the is an admin channel. /// [Required] public bool? IsAdminChannel { get; set; } /// - /// If the is a watchdog channel + /// If the is a watchdog channel. /// [Required] public bool? IsWatchdogChannel { get; set; } /// - /// If the is an updates channel + /// If the is an updates channel. /// [Required] public bool? IsUpdatesChannel { get; set; } /// - /// A custom tag users can define to group channels together + /// A custom tag users can define to group channels together. /// [ResponseOptions] [StringLength(Limits.MaximumStringLength)] diff --git a/src/Tgstation.Server.Api/Models/ChatProvider.cs b/src/Tgstation.Server.Api/Models/ChatProvider.cs index 4fb3e90902..3c9fca1903 100644 --- a/src/Tgstation.Server.Api/Models/ChatProvider.cs +++ b/src/Tgstation.Server.Api/Models/ChatProvider.cs @@ -1,18 +1,18 @@ namespace Tgstation.Server.Api.Models { /// - /// Represents a chat service provider + /// Represents a chat service provider. /// public enum ChatProvider { /// - /// Internet relay chat + /// Internet relay chat. /// Irc, /// - /// Superior chat service + /// Superior chat service. /// - Discord + Discord, } } diff --git a/src/Tgstation.Server.Api/Models/ConfigurationType.cs b/src/Tgstation.Server.Api/Models/ConfigurationType.cs index 350af0c36f..02d625d177 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationType.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationType.cs @@ -1,23 +1,23 @@ namespace Tgstation.Server.Api.Models { /// - /// The type of configuration allowed on an + /// The type of configuration allowed on an . /// public enum ConfigurationType { /// - /// Configuration editing is not allowed + /// Configuration editing is not allowed. /// Disallowed, /// - /// Configuration editing is allowed by all users on all files + /// Configuration editing is allowed by all users on all files. /// HostWrite, /// - /// Co + /// Configuration editing is allowed by only by system identity users and uses their filesystem ACLs. /// - SystemIdentityWrite + SystemIdentityWrite, } } diff --git a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs index afccbbb3ec..88ff9b0d47 100644 --- a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs @@ -1,11 +1,12 @@ -using System; +using System; using System.Linq; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models { /// - /// for + /// for . /// public sealed class DiscordConnectionStringBuilder : ChatConnectionStringBuilder { @@ -29,7 +30,7 @@ namespace Tgstation.Server.Api.Models public DiscordDMOutputDisplayType DMOutputDisplay { get; set; } /// - /// Construct a + /// Initializes a new instance of the class. /// public DiscordConnectionStringBuilder() { @@ -37,12 +38,12 @@ namespace Tgstation.Server.Api.Models } /// - /// Construct a from a + /// Initializes a new instance of the class. /// - /// The connection string + /// The connection string. public DiscordConnectionStringBuilder(string connectionString) { - if(connectionString == null) + if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); var splits = connectionString.Split(';'); @@ -56,7 +57,7 @@ namespace Tgstation.Server.Api.Models } else { - BasedMeme = true; // Oranges said this needs to be true by default :pensive: + BasedMeme = true; // oranges said this needs to be true by default :pensive: } } diff --git a/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs b/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs index 3af08a5daa..5a58ccb3b8 100644 --- a/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs +++ b/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// When the DM output section of Discord deployment embeds should be shown. @@ -18,6 +18,6 @@ namespace Tgstation.Server.Api.Models /// /// Never show. /// - Never + Never, } } diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs index 8f6b1f0f26..c423109e4b 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs @@ -1,23 +1,23 @@ namespace Tgstation.Server.Api.Models { /// - /// DreamDaemon's security level + /// DreamDaemon's security level. /// public enum DreamDaemonSecurity { /// - /// Server is unrestricted in terms of file access and shell commands + /// Server is unrestricted in terms of file access and shell commands. /// Trusted, /// - /// Server will not be able to run shell commands or access files outside it's working directory + /// Server will not be able to run shell commands or access files outside it's working directory. /// Safe, /// - /// Server will not be able to run shell commands or access anything but temporary files. Currently unsupported! + /// Server will not be able to run shell commands or access anything but temporary files. /// - Ultrasafe + Ultrasafe, } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Api/Models/EntityId.cs b/src/Tgstation.Server.Api/Models/EntityId.cs index a33d3f294c..43ac2e7eda 100644 --- a/src/Tgstation.Server.Api/Models/EntityId.cs +++ b/src/Tgstation.Server.Api/Models/EntityId.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Common base of entities with IDs. diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 311afec346..fe57856e60 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; namespace Tgstation.Server.Api.Models diff --git a/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs b/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs index a69f18e483..27ff6b1873 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs @@ -23,4 +23,4 @@ namespace Tgstation.Server.Api.Models return attributes?.FirstOrDefault()?.Description; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Api/Models/FieldPresence.cs b/src/Tgstation.Server.Api/Models/FieldPresence.cs index 4817b8965a..11db2cd2cb 100644 --- a/src/Tgstation.Server.Api/Models/FieldPresence.cs +++ b/src/Tgstation.Server.Api/Models/FieldPresence.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Indicates whether a request field is or . @@ -18,6 +18,6 @@ namespace Tgstation.Server.Api.Models /// /// The field is ignored or should not appear. /// - Ignored + Ignored, } } diff --git a/src/Tgstation.Server.Api/Models/IConfigurationFile.cs b/src/Tgstation.Server.Api/Models/IConfigurationFile.cs index a1c59c0449..6fa8520228 100644 --- a/src/Tgstation.Server.Api/Models/IConfigurationFile.cs +++ b/src/Tgstation.Server.Api/Models/IConfigurationFile.cs @@ -1,9 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { /// - /// Represents a game configuration file. Create and delete actions uncerimonuously overwrite/delete files + /// Represents a game configuration file. Create and delete actions uncerimonuously overwrite/delete files. /// public interface IConfigurationFile { @@ -14,7 +14,7 @@ namespace Tgstation.Server.Api.Models public string? Path { get; set; } /// - /// The MD5 hash of the file when last read by the user. If this doesn't match during update actions, the write will be denied with + /// The MD5 hash of the file when last read by the user. If this doesn't match during update actions, the write will be denied with . /// [ResponseOptions] public string? LastReadHash { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 2f5ad89701..c4ae9dccec 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -1,35 +1,35 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { /// - /// Metadata about a server instance + /// Metadata about a server instance. /// public abstract class Instance : NamedEntity { /// - /// The path to where the is located. Can only be changed while the is offline. Must not exist when the instance is created + /// The path to where the is located. Can only be changed while the is offline. Must not exist when the instance is created. /// [Required] [RequestOptions(FieldPresence.Required, PutOnly = true)] public string? Path { get; set; } /// - /// If the is online + /// If the is online. /// [Required] [RequestOptions(FieldPresence.Ignored, PutOnly = true)] public bool? Online { get; set; } /// - /// If s can be used on the + /// If s can be used on the . /// [Required] [EnumDataType(typeof(ConfigurationType))] public ConfigurationType? ConfigurationType { get; set; } /// - /// The time interval in minutes the repository is automatically pulled and compiles. 0 disables + /// The time interval in minutes the repository is automatically pulled and compiles. 0 disables. /// [Required] public uint? AutoUpdateInterval { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs index 0ec152ab06..ef82e1e5cb 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -8,14 +8,14 @@ namespace Tgstation.Server.Api.Models.Internal public abstract class ChatBotApiBase : ChatBotSettings { /// - /// Channels the Discord bot should listen/announce in + /// Channels the Discord bot should listen/announce in. /// public ICollection? Channels { get; set; } /// - /// Validates are correct for the + /// Validates are correct for the . /// - /// if the are valid for the , otherwise + /// if the are valid for the , otherwise. public bool ValidateProviderChannelTypes() { if (!Provider.HasValue) diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBotSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBotSettings.cs index 0a29dd84ae..4f6a9e2fac 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatBotSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatBotSettings.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Manage the server chat bots + /// Manage the server chat bots. /// public abstract class ChatBotSettings : NamedEntity { /// - /// If the connection is enabled + /// If the connection is enabled. /// public bool? Enabled { get; set; } @@ -27,7 +27,7 @@ namespace Tgstation.Server.Api.Models.Internal public ushort? ChannelLimit { get; set; } /// - /// The used for the connection + /// The used for the connection. /// [Required] [RequestOptions(FieldPresence.Required, PutOnly = true)] @@ -35,7 +35,7 @@ namespace Tgstation.Server.Api.Models.Internal public ChatProvider? Provider { get; set; } /// - /// The information used to connect to the + /// The information used to connect to the . /// [Required] [RequestOptions(FieldPresence.Required, PutOnly = true)] diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs index 36c1f202aa..07dd59b42f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs @@ -1,19 +1,19 @@ -namespace Tgstation.Server.Api.Models.Internal +namespace Tgstation.Server.Api.Models.Internal { /// - /// Helper for building s + /// Helper for building s. /// public abstract class ChatConnectionStringBuilder { /// - /// If the evaluates to a valid + /// If the evaluates to a valid . /// public abstract bool Valid { get; } /// - /// Gets the associated with the + /// Gets the associated with the . /// - /// The associated with the + /// The associated with the . public abstract override string ToString(); } } diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 425d63eec2..eb8862a86c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -10,19 +10,19 @@ namespace Tgstation.Server.Api.Models.Internal public abstract class CompileJob : EntityId { /// - /// The .dme file used for compilation + /// The .dme file used for compilation. /// [Required] public string? DmeName { get; set; } /// - /// Textual output of DM + /// Textual output of DM. /// [Required] public string? Output { get; set; } /// - /// The Game folder the results were compiled into + /// The Game folder the results were compiled into. /// [Required] public Guid? DirectoryName { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs index 4f31e79760..6afac0db9a 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Internal +namespace Tgstation.Server.Api.Models.Internal { /// /// Base class for DreamDaemon API models. @@ -6,13 +6,13 @@ namespace Tgstation.Server.Api.Models.Internal public abstract class DreamDaemonApiBase : DreamDaemonSettings { /// - /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields + /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields. /// [ResponseOptions] public bool? SoftRestart { get; set; } /// - /// If the server is undergoing a soft shutdown + /// If the server is undergoing a soft shutdown. /// [ResponseOptions] public bool? SoftShutdown { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 9ef1614383..2b0b06aff2 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Launch settings for DreamDaemon + /// Launch settings for DreamDaemon. /// public class DreamDaemonLaunchParameters { /// - /// If the BYOND web client can be used to connect to the game server + /// If the BYOND web client can be used to connect to the game server. /// [Required] [ResponseOptions] @@ -32,7 +32,7 @@ namespace Tgstation.Server.Api.Models.Internal public ushort? Port { get; set; } /// - /// The DreamDaemon startup timeout in seconds + /// The DreamDaemon startup timeout in seconds. /// [Required] [ResponseOptions] @@ -65,8 +65,8 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Check if we match a given set of . is excluded. /// - /// The to compare against - /// if they match, otherwise + /// The to compare against. + /// if they match, otherwise. public bool CanApplyWithoutReboot(DreamDaemonLaunchParameters otherParameters) => AllowWebClient == (otherParameters?.AllowWebClient ?? throw new ArgumentNullException(nameof(otherParameters))) && SecurityLevel == otherParameters.SecurityLevel diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index 5d00f5852b..a6550a1e00 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -8,7 +8,7 @@ namespace Tgstation.Server.Api.Models.Internal public class DreamDaemonSettings : DreamDaemonLaunchParameters { /// - /// If the watchdog starts when it's starts + /// If the watchdog starts when it's starts. /// [Required] [ResponseOptions] diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index 32655f3574..28f809fd57 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -1,9 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile + /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile. /// public abstract class DreamMakerSettings { @@ -15,14 +15,14 @@ namespace Tgstation.Server.Api.Models.Internal public string? ProjectName { get; set; } /// - /// The port used during compilation to validate the DMAPI + /// The port used during compilation to validate the DMAPI. /// [Required] [Range(1, 65535)] public ushort? ApiValidationPort { get; set; } /// - /// The level used to validate the DMAPI + /// The level used to validate the DMAPI. /// [Required] public DreamDaemonSecurity? ApiValidationSecurityLevel { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/IGitRemoteInformation.cs b/src/Tgstation.Server.Api/Models/Internal/IGitRemoteInformation.cs index f687c9908e..ff4c81b0ad 100644 --- a/src/Tgstation.Server.Api/Models/Internal/IGitRemoteInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/IGitRemoteInformation.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Internal +namespace Tgstation.Server.Api.Models.Internal { /// /// Provides information about remote providers. @@ -11,12 +11,12 @@ namespace Tgstation.Server.Api.Models.Internal public RemoteGitProvider? RemoteGitProvider { get; } /// - /// If is not this will be set with the owner of the repository + /// If is not this will be set with the owner of the repository. /// public string? RemoteRepositoryOwner { get; } /// - /// If is not this will be set with the name of the repository + /// If is not this will be set with the name of the repository. /// public string? RemoteRepositoryName { get; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs b/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs index ff2c24fa4a..ad0e4d3f1d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs +++ b/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs @@ -1,57 +1,58 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { /// - /// Represents a s permissions in an + /// Represents a s permissions in an . /// public abstract class InstancePermissionSet { /// - /// The of the the belongs to + /// The of the the belongs to. /// [RequestOptions(FieldPresence.Required)] public long PermissionSetId { get; set; } /// - /// The of the + /// The of the . /// [Required] public InstancePermissionSetRights? InstancePermissionSetRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public ByondRights? ByondRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public DreamDaemonRights? DreamDaemonRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public DreamMakerRights? DreamMakerRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public RepositoryRights? RepositoryRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public ChatBotRights? ChatBotRights { get; set; } /// - /// The of the + /// The of the . /// [Required] public ConfigurationRights? ConfigurationRights { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 254be63dc3..54661a2eac 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -1,16 +1,17 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { /// - /// Represents a long running job + /// Represents a long running job. /// public class Job : EntityId { /// - /// English description of the + /// English description of the . /// [Required] public string? Description { get; set; } @@ -22,37 +23,37 @@ namespace Tgstation.Server.Api.Models.Internal public ErrorCode? ErrorCode { get; set; } /// - /// Details of any exceptions caught during the + /// Details of any exceptions caught during the . /// [ResponseOptions] public string? ExceptionDetails { get; set; } /// - /// When the was started + /// When the was started. /// [Required] public DateTimeOffset? StartedAt { get; set; } /// - /// When the stopped + /// When the stopped. /// [ResponseOptions] public DateTimeOffset? StoppedAt { get; set; } /// - /// If the was cancelled + /// If the was cancelled. /// [Required] public bool? Cancelled { get; set; } /// - /// The of if it can be cancelled + /// The of if it can be cancelled. /// [ResponseOptions] public RightsType? CancelRightsType { get; set; } /// - /// The required to cancel the + /// The required to cancel the . /// [ResponseOptions] public ulong? CancelRight { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs index 2a52068888..7420e166a5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index de0ff3ff23..f2219f9063 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -8,14 +8,14 @@ namespace Tgstation.Server.Api.Models.Internal public class RepositorySettings { /// - /// The name of the committer + /// The name of the committer. /// [Required] [StringLength(Limits.MaximumStringLength)] public string? CommitterName { get; set; } /// - /// The e-mail of the committer + /// The e-mail of the committer. /// [Required] [StringLength(Limits.MaximumStringLength)] @@ -23,14 +23,14 @@ namespace Tgstation.Server.Api.Models.Internal public string? CommitterEmail { get; set; } /// - /// The username to access the git repository with + /// The username to access the git repository with. /// [StringLength(Limits.MaximumStringLength)] [ResponseOptions] public string? AccessUser { get; set; } /// - /// The token/password to access the git repository with + /// The token/password to access the git repository with. /// [StringLength(Limits.MaximumStringLength)] [ResponseOptions(Presence = FieldPresence.Ignored)] @@ -49,13 +49,13 @@ namespace Tgstation.Server.Api.Models.Internal public bool? CreateGitHubDeployments { get; set; } /// - /// If test merge commits are signed with the username of the person who merged it. Note this only affects future commits + /// If test merge commits are signed with the username of the person who merged it. Note this only affects future commits. /// [Required] public bool? ShowTestMergeCommitters { get; set; } /// - /// If test merge commits should be kept when auto updating. May cause merge conflicts which will block the update + /// If test merge commits should be kept when auto updating. May cause merge conflicts which will block the update. /// [Required] public bool? AutoUpdatesKeepTestMerges { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index 18acf71ae9..d88aca4e00 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Represents information about a current git revison + /// Represents information about a current git revison. /// public class RevisionInformation { diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs b/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs index f1c486214c..30d379a323 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Tgstation.Server.Api.Models.Internal { diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Internal/ServerUpdateRequest.cs index 1090f47f16..255e33ab60 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerUpdateRequest.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// public sealed class ServerUpdateRequest : ServerUpdate diff --git a/src/Tgstation.Server.Api/Models/Internal/SwarmServer.cs b/src/Tgstation.Server.Api/Models/Internal/SwarmServer.cs index 7c6d55171d..8ce9873fce 100644 --- a/src/Tgstation.Server.Api/Models/Internal/SwarmServer.cs +++ b/src/Tgstation.Server.Api/Models/Internal/SwarmServer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMergeApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/TestMergeApiBase.cs index 4c2556d4e6..036124c2a1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMergeApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMergeApiBase.cs @@ -1,20 +1,20 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Represents a test merge of a remote "pull request". + /// Represents a test merge of a remote commit source. /// public class TestMergeApiBase : TestMergeModelBase { /// - /// The ID of the + /// The ID of the . /// public long Id { get; set; } /// - /// When the was created + /// When the was created. /// [Required] public DateTimeOffset MergedAt { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMergeModelBase.cs b/src/Tgstation.Server.Api/Models/Internal/TestMergeModelBase.cs index fadd01d1e2..20a2821d07 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMergeModelBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMergeModelBase.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { /// - /// Layer of test merge data required internally + /// Layer of test merge data required internally. /// public abstract class TestMergeModelBase : TestMergeParameters { @@ -35,14 +35,16 @@ namespace Tgstation.Server.Api.Models.Internal public string? Author { get; set; } /// - /// Construct a + /// Initializes a new instance of the class. /// - protected TestMergeModelBase() { } + protected TestMergeModelBase() + { + } /// - /// Construct a from a + /// Initializes a new instance of the class. /// - /// The to copy data from + /// The to copy data from. protected TestMergeModelBase(TestMergeModelBase copy) { if (copy == null) diff --git a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs index ab1d096417..5151a1e1bd 100644 --- a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Tgstation.Server.Api.Models.Internal { diff --git a/src/Tgstation.Server.Api/Models/Internal/UserGroup.cs b/src/Tgstation.Server.Api/Models/Internal/UserGroup.cs index 4afd67a346..6e826a585e 100644 --- a/src/Tgstation.Server.Api/Models/Internal/UserGroup.cs +++ b/src/Tgstation.Server.Api/Models/Internal/UserGroup.cs @@ -1,11 +1,11 @@ -namespace Tgstation.Server.Api.Models.Internal +namespace Tgstation.Server.Api.Models.Internal { /// /// Represents a group of users. /// public class UserGroup : NamedEntity { - /// + /// [RequestOptions(FieldPresence.Required)] public override long? Id { diff --git a/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs b/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs index f30e5f84d3..e96a1f1415 100644 --- a/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal @@ -9,20 +9,20 @@ namespace Tgstation.Server.Api.Models.Internal public abstract class UserModelBase : UserName { /// - /// If the is enabled since users cannot be deleted. System users cannot be disabled + /// If the is enabled since users cannot be deleted. System users cannot be disabled. /// [Required] public bool? Enabled { get; set; } /// - /// When the was created + /// When the was created. /// [Required] [RequestOptions(FieldPresence.Ignored)] public DateTimeOffset? CreatedAt { get; set; } /// - /// The SID/UID of the on Windows/POSIX respectively + /// The SID/UID of the on Windows/POSIX respectively. /// [ResponseOptions] [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] diff --git a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs index 4661742d12..b4cb64a811 100644 --- a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Text; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models { /// - /// for + /// for . /// public sealed class IrcConnectionStringBuilder : ChatConnectionStringBuilder { @@ -14,44 +15,46 @@ namespace Tgstation.Server.Api.Models public override bool Valid => Address != null && Port.HasValue && Port != 0 && UseSsl.HasValue && (PasswordType.HasValue ^ Password == null); /// - /// The IP address or URL of the IRC server + /// The IP address or URL of the IRC server. /// public string? Address { get; set; } /// - /// The port the server runs on + /// The port the server runs on. /// public ushort? Port { get; set; } /// - /// The nickname for the bot to use + /// The nickname for the bot to use. /// public string? Nickname { get; set; } /// - /// If the connection should be made using SSL + /// If the connection should be made using SSL. /// public bool? UseSsl { get; set; } /// - /// The optional to use + /// The optional to use. /// public IrcPasswordType? PasswordType { get; set; } /// - /// The optional password to use + /// The optional password to use. /// public string? Password { get; set; } /// - /// Construct an + /// Initializes a new instance of the class. /// - public IrcConnectionStringBuilder() { } + public IrcConnectionStringBuilder() + { + } /// - /// Construct a from a + /// Initializes a new instance of the class. /// - /// The connection string + /// The connection string. public IrcConnectionStringBuilder(string connectionString) { if (connectionString == null) @@ -109,8 +112,10 @@ namespace Tgstation.Server.Api.Models sb.Append(';'); sb.Append(Nickname); sb.Append(';'); - if(UseSsl.HasValue) + + if (UseSsl.HasValue) sb.Append(Convert.ToInt32(UseSsl.Value)); + if (PasswordType.HasValue) { sb.Append(';'); diff --git a/src/Tgstation.Server.Api/Models/IrcPasswordType.cs b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs index 4c612f618a..ad6ac6d9c2 100644 --- a/src/Tgstation.Server.Api/Models/IrcPasswordType.cs +++ b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Api.Models { /// - /// Represents the type of a password for a + /// Represents the type of a password for a . /// public enum IrcPasswordType { @@ -18,6 +18,6 @@ /// /// Use NickServ authentication /// - NickServ + NickServ, } } diff --git a/src/Tgstation.Server.Api/Models/Limits.cs b/src/Tgstation.Server.Api/Models/Limits.cs index d485dc42ce..abe7dd0da8 100644 --- a/src/Tgstation.Server.Api/Models/Limits.cs +++ b/src/Tgstation.Server.Api/Models/Limits.cs @@ -1,9 +1,9 @@ -using System; +using System; namespace Tgstation.Server.Api.Models { /// - /// Sanity limits to prevent users from overloading + /// Sanity limits to prevent users from overloading. /// public static class Limits { diff --git a/src/Tgstation.Server.Api/Models/NamedEntity.cs b/src/Tgstation.Server.Api/Models/NamedEntity.cs index 42dc14d95c..a138ef7eb5 100644 --- a/src/Tgstation.Server.Api/Models/NamedEntity.cs +++ b/src/Tgstation.Server.Api/Models/NamedEntity.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/OAuthConnection.cs b/src/Tgstation.Server.Api/Models/OAuthConnection.cs index 1e6072feb1..f797d6a820 100644 --- a/src/Tgstation.Server.Api/Models/OAuthConnection.cs +++ b/src/Tgstation.Server.Api/Models/OAuthConnection.cs @@ -1,5 +1,6 @@ +using System.ComponentModel.DataAnnotations; + using Newtonsoft.Json; -using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/OAuthProvider.cs b/src/Tgstation.Server.Api/Models/OAuthProvider.cs index cf9adbd91a..716686ed16 100644 --- a/src/Tgstation.Server.Api/Models/OAuthProvider.cs +++ b/src/Tgstation.Server.Api/Models/OAuthProvider.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace Tgstation.Server.Api.Models { /// - /// List of OAuth providers supported by TGS + /// List of OAuth providers supported by TGS. /// [JsonConverter(typeof(StringEnumConverter))] public enum OAuthProvider diff --git a/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs b/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs index 61dd8f04d7..dd34dbe536 100644 --- a/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs +++ b/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/PermissionSet.cs b/src/Tgstation.Server.Api/Models/PermissionSet.cs index 0ffb6801f2..3017349029 100644 --- a/src/Tgstation.Server.Api/Models/PermissionSet.cs +++ b/src/Tgstation.Server.Api/Models/PermissionSet.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models diff --git a/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs b/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs index df0da1312b..c6df292adf 100644 --- a/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs +++ b/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Indicates the remote git host. diff --git a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs index a7406a93f2..72b37a8378 100644 --- a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs index f3097a7107..930601fa6e 100644 --- a/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/ChatBotUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Request/ChatBotUpdateRequest.cs index e53c35fdbe..496812279e 100644 --- a/src/Tgstation.Server.Api/Models/Request/ChatBotUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ChatBotUpdateRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/ConfigurationFileRequest.cs b/src/Tgstation.Server.Api/Models/Request/ConfigurationFileRequest.cs index 37892f3484..feb978a1f8 100644 --- a/src/Tgstation.Server.Api/Models/Request/ConfigurationFileRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ConfigurationFileRequest.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// /// Represents a request to update a configuration file. diff --git a/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs b/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs index eb2c729924..0398d69935 100644 --- a/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/DreamMakerRequest.cs b/src/Tgstation.Server.Api/Models/Request/DreamMakerRequest.cs index 3523f01ab1..0a44811892 100644 --- a/src/Tgstation.Server.Api/Models/Request/DreamMakerRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/DreamMakerRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs index 6194fe16cb..13f74cd76c 100644 --- a/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// /// A request to create an . diff --git a/src/Tgstation.Server.Api/Models/Request/InstancePermissionSetRequest.cs b/src/Tgstation.Server.Api/Models/Request/InstancePermissionSetRequest.cs index 46d1781b00..a2a1062ce3 100644 --- a/src/Tgstation.Server.Api/Models/Request/InstancePermissionSetRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/InstancePermissionSetRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/InstanceUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Request/InstanceUpdateRequest.cs index 49b8ac697e..881f6e1255 100644 --- a/src/Tgstation.Server.Api/Models/Request/InstanceUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/InstanceUpdateRequest.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// /// A request to update an . diff --git a/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs index 902fb2f896..faea7a80e5 100644 --- a/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request diff --git a/src/Tgstation.Server.Api/Models/Request/RepositoryUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Request/RepositoryUpdateRequest.cs index a9cd63f3e1..4aac9add8b 100644 --- a/src/Tgstation.Server.Api/Models/Request/RepositoryUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/RepositoryUpdateRequest.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request @@ -16,12 +17,12 @@ namespace Tgstation.Server.Api.Models.Request public string? CheckoutSha { get; set; } /// - /// Do the equivalent of a git pull. Will attempt to merge unless is also specified in which case a hard reset will be performed after checking out + /// Do the equivalent of a git pull. Will attempt to merge unless is also specified in which case a hard reset will be performed after checking out. /// public bool? UpdateFromOrigin { get; set; } /// - /// for new s. Note that merges that conflict will not be performed + /// for new s. Note that merges that conflict will not be performed. /// public ICollection? NewTestMerges { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Request/UserCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/UserCreateRequest.cs index 697021e6fd..5f3b4e09da 100644 --- a/src/Tgstation.Server.Api/Models/Request/UserCreateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/UserCreateRequest.cs @@ -1,9 +1,9 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// /// For creating a user. /// - #pragma warning disable CA1501 +#pragma warning disable CA1501 public sealed class UserCreateRequest : UserUpdateRequest { } diff --git a/src/Tgstation.Server.Api/Models/Request/UserGroupCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/UserGroupCreateRequest.cs index 0022a1f037..cb53d81743 100644 --- a/src/Tgstation.Server.Api/Models/Request/UserGroupCreateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/UserGroupCreateRequest.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Request +namespace Tgstation.Server.Api.Models.Request { /// /// Request to create a user group. diff --git a/src/Tgstation.Server.Api/Models/Request/UserGroupUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Request/UserGroupUpdateRequest.cs index cbd8d7d595..f2113c14e7 100644 --- a/src/Tgstation.Server.Api/Models/Request/UserGroupUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/UserGroupUpdateRequest.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request { diff --git a/src/Tgstation.Server.Api/Models/Request/UserUpdateRequest.cs b/src/Tgstation.Server.Api/Models/Request/UserUpdateRequest.cs index 6513a546cf..5c64276f2f 100644 --- a/src/Tgstation.Server.Api/Models/Request/UserUpdateRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/UserUpdateRequest.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Request diff --git a/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs b/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs index 26184bd666..e8332a80c8 100644 --- a/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs +++ b/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models { @@ -19,7 +19,7 @@ namespace Tgstation.Server.Api.Models public bool PutOnly { get; set; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public RequestOptionsAttribute(FieldPresence presence) diff --git a/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs b/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs index 301d87976f..ab8ce0aba6 100644 --- a/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs @@ -1,19 +1,19 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents administrative server information + /// Represents administrative server information. /// public sealed class AdministrationResponse { /// - /// The GitHub repository the server is built to recieve updates from + /// The GitHub repository the server is built to recieve updates from. /// public Uri? TrackedRepositoryUrl { get; set; } /// - /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is not equal to 4 the update cannot be applied due to API changes + /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is not equal to 4 the update cannot be applied due to API changes. /// public Version? LatestVersion { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Response/ByondInstallResponse.cs b/src/Tgstation.Server.Api/Models/Response/ByondInstallResponse.cs index 3cebc0a6a6..1b66a459b4 100644 --- a/src/Tgstation.Server.Api/Models/Response/ByondInstallResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ByondInstallResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs b/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs index e54af87526..47b2776361 100644 --- a/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/ChatBotResponse.cs b/src/Tgstation.Server.Api/Models/Response/ChatBotResponse.cs index 716f8deb80..4b52fbcf00 100644 --- a/src/Tgstation.Server.Api/Models/Response/ChatBotResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ChatBotResponse.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs b/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs index 0739e58347..60444d9c3c 100644 --- a/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs b/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs index 062c0de625..6b07dc2f65 100644 --- a/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Response +namespace Tgstation.Server.Api.Models.Response { /// /// Response when reading configuration files. @@ -13,12 +13,12 @@ namespace Tgstation.Server.Api.Models.Response public string? LastReadHash { get; set; } /// - /// If represents a directory + /// If represents a directory. /// public bool? IsDirectory { get; set; } /// - /// If access to the file was denied for the operation + /// If access to the file was denied for the operation. /// [ResponseOptions] public bool? AccessDenied { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs index 2fcbaa6189..13fb6ea5da 100644 --- a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs @@ -1,10 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server + /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server. /// public sealed class DreamDaemonResponse : DreamDaemonApiBase { @@ -28,20 +29,20 @@ namespace Tgstation.Server.Api.Models.Response public WatchdogStatus? Status { get; set; } /// - /// The current of . May be downgraded due to requirements of + /// The current of . May be downgraded due to requirements of . /// [EnumDataType(typeof(DreamDaemonSecurity))] [ResponseOptions] public DreamDaemonSecurity? CurrentSecurity { get; set; } /// - /// The port the running instance is set to + /// The port the running instance is set to. /// [ResponseOptions] public ushort? CurrentPort { get; set; } /// - /// The webclient status the running instance is set to + /// The webclient status the running instance is set to. /// [ResponseOptions] public bool? CurrentAllowWebclient { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Response/DreamMakerResponse.cs b/src/Tgstation.Server.Api/Models/Response/DreamMakerResponse.cs index d114c7a43e..7145973914 100644 --- a/src/Tgstation.Server.Api/Models/Response/DreamMakerResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/DreamMakerResponse.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/ErrorMessageResponse.cs b/src/Tgstation.Server.Api/Models/Response/ErrorMessageResponse.cs index 7cb35319e9..914d9ecc3a 100644 --- a/src/Tgstation.Server.Api/Models/Response/ErrorMessageResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ErrorMessageResponse.cs @@ -1,20 +1,20 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents an error message returned by the server + /// Represents an error message returned by the server. /// public sealed class ErrorMessageResponse { /// - /// The version of the API the server is using + /// The version of the API the server is using. /// public Version? ServerApiVersion { get; set; } /// - /// A human readable description of the error + /// A human readable description of the error. /// public string? Message { get; set; } @@ -31,12 +31,14 @@ namespace Tgstation.Server.Api.Models.Response public ErrorCode ErrorCode { get; set; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - public ErrorMessageResponse() { } + public ErrorMessageResponse() + { + } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The . public ErrorMessageResponse(ErrorCode errorCode) diff --git a/src/Tgstation.Server.Api/Models/Response/FileTicketResponse.cs b/src/Tgstation.Server.Api/Models/Response/FileTicketResponse.cs index 1e94f65b5e..462faca34a 100644 --- a/src/Tgstation.Server.Api/Models/Response/FileTicketResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/FileTicketResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Response +namespace Tgstation.Server.Api.Models.Response { /// /// Response for when file transfers are necessary. diff --git a/src/Tgstation.Server.Api/Models/Response/InstancePermissionSetResponse.cs b/src/Tgstation.Server.Api/Models/Response/InstancePermissionSetResponse.cs index 811fef574e..dabaf0b06d 100644 --- a/src/Tgstation.Server.Api/Models/Response/InstancePermissionSetResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/InstancePermissionSetResponse.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/InstanceResponse.cs b/src/Tgstation.Server.Api/Models/Response/InstanceResponse.cs index 9b5e9dd03d..6ca3937782 100644 --- a/src/Tgstation.Server.Api/Models/Response/InstanceResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/InstanceResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Response +namespace Tgstation.Server.Api.Models.Response { /// /// Server response for s. diff --git a/src/Tgstation.Server.Api/Models/Response/JobResponse.cs b/src/Tgstation.Server.Api/Models/Response/JobResponse.cs index 38613ef26f..6c04aa919c 100644 --- a/src/Tgstation.Server.Api/Models/Response/JobResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/JobResponse.cs @@ -1,23 +1,23 @@ -namespace Tgstation.Server.Api.Models.Response +namespace Tgstation.Server.Api.Models.Response { /// - /// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job + /// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job. /// public sealed class JobResponse : Internal.Job { /// - /// The that started the job + /// The that started the job. /// public UserName? StartedBy { get; set; } /// - /// The that cancelled the job + /// The that cancelled the job. /// [ResponseOptions] public UserName? CancelledBy { get; set; } /// - /// Optional progress between 0 and 100 inclusive + /// Optional progress between 0 and 100 inclusive. /// [ResponseOptions] public int? Progress { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Response/LogFileResponse.cs b/src/Tgstation.Server.Api/Models/Response/LogFileResponse.cs index 1bfa2f5030..05c05e8aa0 100644 --- a/src/Tgstation.Server.Api/Models/Response/LogFileResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/LogFileResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/PaginatedResponse.cs b/src/Tgstation.Server.Api/Models/Response/PaginatedResponse.cs index fba3c698a6..2b5d0d9372 100644 --- a/src/Tgstation.Server.Api/Models/Response/PaginatedResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/PaginatedResponse.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Response diff --git a/src/Tgstation.Server.Api/Models/Response/RepositoryResponse.cs b/src/Tgstation.Server.Api/Models/Response/RepositoryResponse.cs index 14a9fe2f73..1596af26bb 100644 --- a/src/Tgstation.Server.Api/Models/Response/RepositoryResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/RepositoryResponse.cs @@ -1,10 +1,11 @@ -using System; +using System; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents a git repository + /// Represents a git repository. /// public sealed class RepositoryResponse : RepositoryApiBase, IGitRemoteInformation { diff --git a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs index 0efe4dc4bc..96b5306702 100644 --- a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Tgstation.Server.Api.Models.Response @@ -9,12 +9,12 @@ namespace Tgstation.Server.Api.Models.Response public sealed class ServerInformationResponse : Internal.ServerInformationBase { /// - /// The version of the host + /// The version of the host. /// public Version? Version { get; set; } /// - /// The version of the host + /// The version of the host. /// public Version? ApiVersion { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs b/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs index a3ff1e7159..25c7c935aa 100644 --- a/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models.Response +namespace Tgstation.Server.Api.Models.Response { /// public sealed class ServerUpdateResponse : ServerUpdate diff --git a/src/Tgstation.Server.Api/Models/Response/SwarmServerResponse.cs b/src/Tgstation.Server.Api/Models/Response/SwarmServerResponse.cs index 1e11ad9b45..a9e5ae9cb0 100644 --- a/src/Tgstation.Server.Api/Models/Response/SwarmServerResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/SwarmServerResponse.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { diff --git a/src/Tgstation.Server.Api/Models/Response/TokenResponse.cs b/src/Tgstation.Server.Api/Models/Response/TokenResponse.cs index 21a96b5388..cd143ede8c 100644 --- a/src/Tgstation.Server.Api/Models/Response/TokenResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/TokenResponse.cs @@ -1,19 +1,19 @@ -using System; +using System; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents a JWT returned by the API + /// Represents a JWT returned by the API. /// public sealed class TokenResponse { /// - /// The value of the JWT + /// The value of the JWT. /// public string? Bearer { get; set; } /// - /// When the expires + /// When the expires. /// public DateTimeOffset ExpiresAt { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs b/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs index d72f9eeaae..3a6566215b 100644 --- a/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response @@ -7,7 +8,7 @@ namespace Tgstation.Server.Api.Models.Response public sealed class UserGroupResponse : UserGroup { /// - /// The s the has. + /// The s the has. /// public ICollection? Users { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Response/UserResponse.cs b/src/Tgstation.Server.Api/Models/Response/UserResponse.cs index 09b0f03c8f..608f66976b 100644 --- a/src/Tgstation.Server.Api/Models/Response/UserResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/UserResponse.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response @@ -7,7 +8,7 @@ namespace Tgstation.Server.Api.Models.Response public class UserResponse : UserApiBase { /// - /// The who created this + /// The who created this . /// [Required] public UserName? CreatedBy { get; set; } diff --git a/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs b/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs index a8c42e0f4c..36fecd93e2 100644 --- a/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs +++ b/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index 4c47bde70c..1bef1025f0 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Tgstation.Server.Api.Models { @@ -6,18 +6,18 @@ namespace Tgstation.Server.Api.Models public sealed class RevisionInformation : Internal.RevisionInformation { /// - /// The that was created with this + /// The that was created with this . /// [ResponseOptions] public TestMerge? PrimaryTestMerge { get; set; } /// - /// The s active in the + /// The s active in the . /// public ICollection? ActiveTestMerges { get; set; } /// - /// The s made from the + /// The s made from the . /// public ICollection? CompileJobs { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/ServerUpdate.cs b/src/Tgstation.Server.Api/Models/ServerUpdate.cs index 6af9e3251e..71a94da6b8 100644 --- a/src/Tgstation.Server.Api/Models/ServerUpdate.cs +++ b/src/Tgstation.Server.Api/Models/ServerUpdate.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs index 5ab802f96f..3a4a46f5c2 100644 --- a/src/Tgstation.Server.Api/Models/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models { diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs index f779a42d98..174bb6886b 100644 --- a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs +++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs @@ -1,9 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { /// - /// Parameters for creating a + /// Parameters for creating a . /// public class TestMergeParameters { @@ -13,14 +13,14 @@ namespace Tgstation.Server.Api.Models public int Number { get; set; } /// - /// The sha of the test merge revision to merge. If not specified, the latest commit shall be used (semi-unsafe) + /// The sha of the test merge revision to merge. If not specified, the latest commit from the source will be used. /// [Required] [StringLength(40)] public virtual string? TargetCommitSha { get; set; } /// - /// Optional comment about the test + /// Optional comment about the test. /// [ResponseOptions] [StringLength(Limits.MaximumStringLength)] diff --git a/src/Tgstation.Server.Api/Models/UserName.cs b/src/Tgstation.Server.Api/Models/UserName.cs index 8044489a2d..c5d5f30604 100644 --- a/src/Tgstation.Server.Api/Models/UserName.cs +++ b/src/Tgstation.Server.Api/Models/UserName.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Base class for user names. @@ -13,6 +13,12 @@ namespace Tgstation.Server.Api.Models set => base.Name = value; } + /// + /// Create a copy of the . + /// + /// A new copied from . + public UserName CreateUserName() => CreateUserName(); + /// /// Create a copy of the as a given . /// @@ -21,13 +27,7 @@ namespace Tgstation.Server.Api.Models protected virtual TResultType CreateUserName() where TResultType : UserName, new() => new TResultType { Id = Id, - Name = Name + Name = Name, }; - - /// - /// Create a copy of the . - /// - /// A new copied from . - public UserName CreateUserName() => CreateUserName(); } } diff --git a/src/Tgstation.Server.Api/Models/WatchdogStatus.cs b/src/Tgstation.Server.Api/Models/WatchdogStatus.cs index 5250f3d740..0950b541d9 100644 --- a/src/Tgstation.Server.Api/Models/WatchdogStatus.cs +++ b/src/Tgstation.Server.Api/Models/WatchdogStatus.cs @@ -25,4 +25,4 @@ /// DelayedRestart, } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Api/Properties/ApiVersionAttribute.cs b/src/Tgstation.Server.Api/Properties/ApiVersionAttribute.cs index 87de47ad51..dfb926078f 100644 --- a/src/Tgstation.Server.Api/Properties/ApiVersionAttribute.cs +++ b/src/Tgstation.Server.Api/Properties/ApiVersionAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; namespace Tgstation.Server.Api.Properties @@ -22,7 +22,7 @@ namespace Tgstation.Server.Api.Properties public string RawApiVersion { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public ApiVersionAttribute( diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index d0747d9f45..9d32a368d2 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 100d64f2b7..f6b75ece0c 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { diff --git a/src/Tgstation.Server.Api/Rights/ChatBotRights.cs b/src/Tgstation.Server.Api/Rights/ChatBotRights.cs index 9deee84290..bf8c3c9aff 100644 --- a/src/Tgstation.Server.Api/Rights/ChatBotRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatBotRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs index 48a192fdcd..846d35737f 100644 --- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { @@ -31,6 +31,6 @@ namespace Tgstation.Server.Api.Rights /// /// User may delete empty folders if the allows it. /// - Delete = 8 + Delete = 8, } } diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index ad69c359c7..7e6a474127 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 680a609859..08155c4807 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index e438734d90..8bee49ea45 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -1,9 +1,9 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { /// - /// Rights for managing s + /// Rights for managing s. /// [Flags] public enum InstanceManagerRights : ulong diff --git a/src/Tgstation.Server.Api/Rights/InstancePermissionSetRights.cs b/src/Tgstation.Server.Api/Rights/InstancePermissionSetRights.cs index 6b2dc5814d..6b22d433a4 100644 --- a/src/Tgstation.Server.Api/Rights/InstancePermissionSetRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstancePermissionSetRights.cs @@ -1,9 +1,9 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { /// - /// Rights for an + /// Rights for an . /// [Flags] public enum InstancePermissionSetRights : ulong @@ -26,6 +26,6 @@ namespace Tgstation.Server.Api.Rights /// /// Allow adding additional s to the . /// - Create = 4 + Create = 4, } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 4d0d452287..eeb6e7646c 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Api.Rights { @@ -76,6 +76,6 @@ namespace Tgstation.Server.Api.Rights /// /// User may cancel clone jobs. /// - CancelClone = 4096 + CancelClone = 4096, } } diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 97bfc6be2d..8860391110 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -1,16 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; namespace Tgstation.Server.Api.Rights { /// - /// Helper for s + /// Helper for s. /// public static class RightsHelper { /// - /// Map of s to their respective flag s + /// Map of s to their respective flag s. /// static readonly IReadOnlyDictionary TypeMap = new Dictionary { @@ -22,29 +22,29 @@ namespace Tgstation.Server.Api.Rights { RightsType.DreamDaemon, typeof(DreamDaemonRights) }, { RightsType.ChatBots, typeof(ChatBotRights) }, { RightsType.Configuration, typeof(ConfigurationRights) }, - { RightsType.InstancePermissionSet, typeof(InstancePermissionSetRights) } + { RightsType.InstancePermissionSet, typeof(InstancePermissionSetRights) }, }; /// - /// Map a given to its respective + /// Map a given to its respective . /// - /// The to lookup - /// The of the given + /// The to lookup. + /// The of the given . public static Type RightToType(RightsType rightsType) => TypeMap[rightsType]; /// - /// Gets the role claim name used for a given + /// Gets the role claim name used for a given . /// - /// The - /// The - /// A representing the claim role name + /// The . + /// The . + /// A representing the claim role name. public static string RoleNames(TRight right) where TRight : Enum { IEnumerable GetRoleNames() { - foreach (Enum J in Enum.GetValues(right.GetType())) - if (Convert.ToInt32(J, CultureInfo.InvariantCulture) != 0 && right.HasFlag(J)) - yield return String.Concat(typeof(TRight).Name, '.', J.ToString()); + foreach (Enum rightValue in Enum.GetValues(right.GetType())) + if (Convert.ToInt32(rightValue, CultureInfo.InvariantCulture) != 0 && right.HasFlag(rightValue)) + yield return String.Concat(typeof(TRight).Name, '.', rightValue.ToString()); } var names = GetRoleNames(); @@ -52,11 +52,11 @@ namespace Tgstation.Server.Api.Rights } /// - /// Gets the role claim name used for a given and + /// Gets the role claim name used for a given and . /// - /// The - /// The right value - /// A representing the claim role name + /// The . + /// The right value. + /// A representing the claim role name. public static string RoleName(RightsType rightsType, Enum right) { var enumType = RightToType(rightsType); @@ -64,10 +64,10 @@ namespace Tgstation.Server.Api.Rights } /// - /// Check if a given is meant for an + /// Check if a given is meant for an . /// - /// The to check - /// if is an instance right, otherwise + /// The to check. + /// if is an instance right, otherwise. public static bool IsInstanceRight(RightsType rightsType) => !(rightsType == RightsType.Administration || rightsType == RightsType.InstanceManager); /// @@ -78,8 +78,8 @@ namespace Tgstation.Server.Api.Rights public static TRight AllRights() where TRight : Enum { ulong rights = 0; - foreach (Enum J in Enum.GetValues(typeof(TRight))) - rights |= Convert.ToUInt64(J, CultureInfo.InvariantCulture); + foreach (Enum right in Enum.GetValues(typeof(TRight))) + rights |= Convert.ToUInt64(right, CultureInfo.InvariantCulture); // cri evertim return (TRight)(object)rights; diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index 2787c63f48..ae40fe46be 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -1,7 +1,7 @@ -namespace Tgstation.Server.Api.Rights +namespace Tgstation.Server.Api.Rights { /// - /// The type of rights a model uses + /// The type of rights a model uses. /// public enum RightsType : ulong { @@ -48,6 +48,6 @@ namespace Tgstation.Server.Api.Rights /// /// /// - InstancePermissionSet + InstancePermissionSet, } } diff --git a/src/Tgstation.Server.Api/Routes.cs b/src/Tgstation.Server.Api/Routes.cs index 7825f1bb5d..ab8bd05c23 100644 --- a/src/Tgstation.Server.Api/Routes.cs +++ b/src/Tgstation.Server.Api/Routes.cs @@ -1,15 +1,14 @@ -using System; -using System.Globalization; +using System; namespace Tgstation.Server.Api { /// - /// Routes to a server actions + /// Routes to a server actions. /// public static class Routes { /// - /// The root controller + /// The root controller. /// public const string Root = "/"; @@ -49,17 +48,17 @@ namespace Tgstation.Server.Api public const string Repository = Root + "Repository"; /// - /// The DreamDaemon controller + /// The DreamDaemon controller. /// public const string DreamDaemon = Root + "DreamDaemon"; /// - /// For accessing DD diagnostics + /// For accessing DD diagnostics. /// public const string Diagnostics = DreamDaemon + "/Diagnostics"; /// - /// The configuration controller + /// The configuration controller. /// public const string Configuration = Root + "Config"; @@ -69,7 +68,7 @@ namespace Tgstation.Server.Api public const string File = "File"; /// - /// Full combination of and + /// Full combination of and . /// public const string ConfigurationFile = Configuration + "/" + File; @@ -79,17 +78,17 @@ namespace Tgstation.Server.Api public const string InstancePermissionSet = Root + "InstancePermissionSet"; /// - /// The chat bot controller + /// The chat bot controller. /// public const string Chat = Root + "Chat"; /// - /// The deployment controller + /// The deployment controller. /// public const string DreamMaker = Root + "DreamMaker"; /// - /// The jobs controller + /// The jobs controller. /// public const string Jobs = Root + "Job"; @@ -99,24 +98,24 @@ namespace Tgstation.Server.Api public const string Transfer = Root + "Transfer"; /// - /// The postfix for list operations + /// The postfix for list operations. /// public const string List = "List"; /// - /// Apply an postfix to a + /// Apply an postfix to a . /// - /// The route - /// The ID - /// The with appended - public static string SetID(string route, long id) => String.Format(CultureInfo.InvariantCulture, "{0}/{1}", route, id); + /// The route. + /// The ID. + /// The with appended. + public static string SetID(string route, long id) => $"{route}/{id}"; /// - /// Get the /List postfix for a + /// Get the /List postfix for a . /// - /// The route - /// The with /List appended - public static string ListRoute(string route) => String.Format(CultureInfo.InvariantCulture, "{0}/{1}", route, List); + /// The route. + /// The with /List appended. + public static string ListRoute(string route) => $"{route}/{List}"; /// /// Sanitize a path for use in a GET . diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 377ce49ca2..af11456dbd 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -52,6 +52,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Api/VersionExtensions.cs b/src/Tgstation.Server.Api/VersionExtensions.cs index 9e00360ddb..9a194f0abb 100644 --- a/src/Tgstation.Server.Api/VersionExtensions.cs +++ b/src/Tgstation.Server.Api/VersionExtensions.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Api { /// - /// Extensions for the . + /// Extensions for the class. /// public static class VersionExtensions { diff --git a/src/Tgstation.Server.Client/AdministrationClient.cs b/src/Tgstation.Server.Client/AdministrationClient.cs index ea044ba0bd..ce4fcac728 100644 --- a/src/Tgstation.Server.Client/AdministrationClient.cs +++ b/src/Tgstation.Server.Client/AdministrationClient.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Web; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -14,12 +15,13 @@ namespace Tgstation.Server.Client sealed class AdministrationClient : PaginatedClient, IAdministrationClient { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . public AdministrationClient(IApiClient apiClient) : base(apiClient) - { } + { + } /// public Task Read(CancellationToken cancellationToken) => ApiClient.Read(Routes.Administration, cancellationToken); diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index f1afee6b3f..c155355b7e 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -1,7 +1,4 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -13,6 +10,11 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -40,17 +42,17 @@ namespace Tgstation.Server.Client } /// - /// The for the + /// The for the . /// readonly IHttpClient httpClient; /// - /// The s used by the + /// The s used by the . /// readonly List requestLoggers; /// - /// Backing field for + /// Backing field for . /// readonly ApiHeaders? tokenRefreshHeaders; @@ -60,16 +62,25 @@ namespace Tgstation.Server.Client readonly SemaphoreSlim semaphoreSlim; /// - /// Backing field for + /// Backing field for . /// ApiHeaders headers; + /// + /// Get the to use. + /// + /// A new instance. static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new[] { new VersionConverter() } + Converters = new[] { new VersionConverter() }, }; + /// + /// Handle a bad HTTP . + /// + /// The . + /// The JSON if any. static void HandleBadResponse(HttpResponseMessage response, string json) { ErrorMessageResponse? errorMessage = null; @@ -78,7 +89,9 @@ namespace Tgstation.Server.Client // check if json serializes to an error message errorMessage = JsonConvert.DeserializeObject(json, GetSerializerSettings()); } - catch (JsonException) { } + catch (JsonException) + { + } #pragma warning disable IDE0010 // Add missing cases switch (response.StatusCode) @@ -112,12 +125,12 @@ namespace Tgstation.Server.Client } /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) { this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); @@ -136,157 +149,6 @@ namespace Tgstation.Server.Client semaphoreSlim.Dispose(); } - /// - /// Main request method - /// - /// The body . - /// The resulting POCO type. - /// The route to run - /// The body of the request - /// The method of the request - /// The optional instance for the request - /// If this is a token refresh operation. - /// The for the operation - /// A resulting in the response on success - Task RunRequest( - string route, - TBody? body, - HttpMethod method, - long? instanceId, - bool tokenRefresh, - CancellationToken cancellationToken) - where TBody : class - { - HttpContent? content = null; - if(body != null) - content = new StringContent( - JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, GetSerializerSettings()), - Encoding.UTF8, - MediaTypeNames.Application.Json); - - return RunRequest( - route, - content, - method, - instanceId, - tokenRefresh, - cancellationToken); - } - - /// - /// Main request method - /// - /// The resulting POCO type - /// The route to run - /// The of the request if any. - /// The method of the request - /// The optional instance for the request - /// If this is a token refresh operation. - /// The for the operation - /// A resulting in the response on success - async Task RunRequest( - string route, - HttpContent? content, - HttpMethod method, - long? instanceId, - bool tokenRefresh, - CancellationToken cancellationToken) - { - if (route == null) - throw new ArgumentNullException(nameof(route)); - if (method == null) - throw new ArgumentNullException(nameof(method)); - if (content == null && (method == HttpMethod.Post || method == HttpMethod.Put)) - throw new InvalidOperationException("content cannot be null for POST or PUT!"); - - HttpResponseMessage response; - var fullUri = new Uri(Url, route); - var serializerSettings = GetSerializerSettings(); - var fileDownload = typeof(TResult) == typeof(Stream); - using (var request = new HttpRequestMessage(method, fullUri)) - { - if (content != null) - request.Content = content; - - var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers; - headersToUse.SetRequestHeaders(request.Headers, instanceId); - - if (fileDownload) - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Octet)); - - await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false); - - response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - } - - try - { - await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false); - - // just stream - if (fileDownload && response.IsSuccessStatusCode) - return (TResult)(object)await CachedResponseStream.Create(response).ConfigureAwait(false); - } - catch - { - response.Dispose(); - throw; - } - - using (response) - { - var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) - { - if (!tokenRefresh - && response.StatusCode == HttpStatusCode.Unauthorized - && await RefreshToken(cancellationToken).ConfigureAwait(false)) - return await RunRequest(route, content, method, instanceId, false, cancellationToken).ConfigureAwait(false); - HandleBadResponse(response, json); - } - - if (String.IsNullOrWhiteSpace(json)) - json = JsonConvert.SerializeObject(new object()); - - try - { - return JsonConvert.DeserializeObject(json, serializerSettings) !; - } - catch (JsonException) - { - throw new UnrecognizedResponseException(response); - } - } - } - - async Task RefreshToken(CancellationToken cancellationToken) - { - if (tokenRefreshHeaders == null) - return false; - - var startingToken = headers.Token; - await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - if (startingToken != headers.Token) - return true; - - var token = await RunRequest(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken); - headers = new ApiHeaders(headers.UserAgent!, token.Bearer!); - } - catch (ClientException) - { - return false; - } - finally - { - semaphoreSlim.Release(); - } - - return true; - } - /// public Task Create(string route, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Put, null, false, cancellationToken); @@ -376,7 +238,7 @@ namespace Tgstation.Server.Client false, cancellationToken) .ConfigureAwait(false); - streamContent = null; //CA2000 + streamContent = null; // CA2000 } finally { @@ -384,5 +246,162 @@ namespace Tgstation.Server.Client } } } + + /// + /// Attempt to refresh the bearer token in the . + /// + /// The for the operation. + /// A resulting in if the refresh was successful, otherwise. + async Task RefreshToken(CancellationToken cancellationToken) + { + if (tokenRefreshHeaders == null) + return false; + + var startingToken = headers.Token; + await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (startingToken != headers.Token) + return true; + + var token = await RunRequest(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken); + headers = new ApiHeaders(headers.UserAgent!, token.Bearer!); + } + catch (ClientException) + { + return false; + } + finally + { + semaphoreSlim.Release(); + } + + return true; + } + + /// + /// Main request method. + /// + /// The body . + /// The resulting POCO type. + /// The route to run. + /// The body of the request. + /// The method of the request. + /// The optional instance for the request. + /// If this is a token refresh operation. + /// The for the operation. + /// A resulting in the response on success. + Task RunRequest( + string route, + TBody? body, + HttpMethod method, + long? instanceId, + bool tokenRefresh, + CancellationToken cancellationToken) + where TBody : class + { + HttpContent? content = null; + if (body != null) + content = new StringContent( + JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, GetSerializerSettings()), + Encoding.UTF8, + MediaTypeNames.Application.Json); + + return RunRequest( + route, + content, + method, + instanceId, + tokenRefresh, + cancellationToken); + } + + /// + /// Main request method. + /// + /// The resulting POCO type. + /// The route to run. + /// The of the request if any. + /// The method of the request. + /// The optional instance for the request. + /// If this is a token refresh operation. + /// The for the operation. + /// A resulting in the response on success. + async Task RunRequest( + string route, + HttpContent? content, + HttpMethod method, + long? instanceId, + bool tokenRefresh, + CancellationToken cancellationToken) + { + if (route == null) + throw new ArgumentNullException(nameof(route)); + if (method == null) + throw new ArgumentNullException(nameof(method)); + if (content == null && (method == HttpMethod.Post || method == HttpMethod.Put)) + throw new InvalidOperationException("content cannot be null for POST or PUT!"); + + HttpResponseMessage response; + var fullUri = new Uri(Url, route); + var serializerSettings = GetSerializerSettings(); + var fileDownload = typeof(TResult) == typeof(Stream); + using (var request = new HttpRequestMessage(method, fullUri)) + { + if (content != null) + request.Content = content; + + var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers; + headersToUse.SetRequestHeaders(request.Headers, instanceId); + + if (fileDownload) + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Octet)); + + await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false); + + response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + + try + { + await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false); + + // just stream + if (fileDownload && response.IsSuccessStatusCode) + return (TResult)(object)await CachedResponseStream.Create(response).ConfigureAwait(false); + } + catch + { + response.Dispose(); + throw; + } + + using (response) + { + var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + if (!tokenRefresh + && response.StatusCode == HttpStatusCode.Unauthorized + && await RefreshToken(cancellationToken).ConfigureAwait(false)) + return await RunRequest(route, content, method, instanceId, false, cancellationToken).ConfigureAwait(false); + HandleBadResponse(response, json); + } + + if (String.IsNullOrWhiteSpace(json)) + json = JsonConvert.SerializeObject(new object()); + + try + { + var result = JsonConvert.DeserializeObject(json, serializerSettings); + return result!; + } + catch (JsonException) + { + throw new UnrecognizedResponseException(response); + } + } + } } } diff --git a/src/Tgstation.Server.Client/ApiClientFactory.cs b/src/Tgstation.Server.Client/ApiClientFactory.cs index 02ff485b3a..26881d2d26 100644 --- a/src/Tgstation.Server.Client/ApiClientFactory.cs +++ b/src/Tgstation.Server.Client/ApiClientFactory.cs @@ -1,4 +1,5 @@ using System; + using Tgstation.Server.Api; namespace Tgstation.Server.Client @@ -9,4 +10,4 @@ namespace Tgstation.Server.Client /// public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClientImplementation(), url, apiHeaders, tokenRefreshHeaders); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Client/ApiConflictException.cs b/src/Tgstation.Server.Client/ApiConflictException.cs index bfd1b1e51d..02dd82ea94 100644 --- a/src/Tgstation.Server.Client/ApiConflictException.cs +++ b/src/Tgstation.Server.Client/ApiConflictException.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client @@ -10,29 +11,36 @@ namespace Tgstation.Server.Client public sealed class ApiConflictException : ApiException { /// - /// Construct an using an + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . public ApiConflictException(ErrorMessageResponse? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage) - { } + { + } /// - /// Construct an + /// Initializes a new instance of the class. /// - public ApiConflictException() { } + public ApiConflictException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public ApiConflictException(string message) : base(message) { } + /// The message for the . + public ApiConflictException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public ApiConflictException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public ApiConflictException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/ApiException.cs b/src/Tgstation.Server.Client/ApiException.cs index d8f24addac..3a90907254 100644 --- a/src/Tgstation.Server.Client/ApiException.cs +++ b/src/Tgstation.Server.Client/ApiException.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -26,7 +27,7 @@ namespace Tgstation.Server.Client public ErrorCode? ErrorCode { get; } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The returned from the API. /// The . @@ -40,7 +41,7 @@ namespace Tgstation.Server.Client } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The . /// The . @@ -51,14 +52,14 @@ namespace Tgstation.Server.Client } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// protected ApiException() { } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The message for the . protected ApiException(string message) : base(message) @@ -66,7 +67,7 @@ namespace Tgstation.Server.Client } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The message for the . /// The inner for the base . diff --git a/src/Tgstation.Server.Client/CachedResponseStream.cs b/src/Tgstation.Server.Client/CachedResponseStream.cs index 9ad446f75c..d50d14fbc2 100644 --- a/src/Tgstation.Server.Client/CachedResponseStream.cs +++ b/src/Tgstation.Server.Client/CachedResponseStream.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Net.Http; using System.Threading.Tasks; @@ -30,27 +30,6 @@ namespace Tgstation.Server.Client return new CachedResponseStream(response, stream); } - /// - /// Initializes a new instance of the . - /// - /// The value of . - /// The value of . - CachedResponseStream(HttpResponseMessage response, Stream responseStream) - { - this.response = response; - this.responseStream = responseStream; - } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - return; - responseStream.Dispose(); - response.Dispose(); - } - /// public override async ValueTask DisposeAsync() { @@ -92,5 +71,26 @@ namespace Tgstation.Server.Client /// public override void Write(byte[] buffer, int offset, int count) => responseStream.Write(buffer, offset, count); + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + responseStream.Dispose(); + response.Dispose(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + CachedResponseStream(HttpResponseMessage response, Stream responseStream) + { + this.response = response; + this.responseStream = responseStream; + } } } diff --git a/src/Tgstation.Server.Client/ClientException.cs b/src/Tgstation.Server.Client/ClientException.cs index 92926f26e5..4647f7bd43 100644 --- a/src/Tgstation.Server.Client/ClientException.cs +++ b/src/Tgstation.Server.Client/ClientException.cs @@ -1,21 +1,21 @@ -using System; +using System; using System.Net; using System.Net.Http; namespace Tgstation.Server.Client { /// - /// Exceptions thrown by s + /// Exceptions thrown by s. /// public abstract class ClientException : Exception { /// - /// The of the + /// The of the . /// public HttpResponseMessage? ResponseMessage { get; } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The that generated the . /// The message for the . @@ -25,21 +25,27 @@ namespace Tgstation.Server.Client } /// - /// Construct a + /// Initializes a new instance of the class. /// - protected ClientException() { } + protected ClientException() + { + } /// - /// Construct a with a + /// Initializes a new instance of the class. /// - /// The message for the - protected ClientException(string message) : base(message) { } + /// The message for the . + protected ClientException(string message) : base(message) + { + } /// - /// Construct a with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - protected ClientException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + protected ClientException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/Components/ByondClient.cs b/src/Tgstation.Server.Client/Components/ByondClient.cs index 9f6bf84823..cd4fbf76a6 100644 --- a/src/Tgstation.Server.Client/Components/ByondClient.cs +++ b/src/Tgstation.Server.Client/Components/ByondClient.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -14,15 +15,15 @@ namespace Tgstation.Server.Client.Components sealed class ByondClient : PaginatedClient, IByondClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public ByondClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs index a417bfb2ea..d53b0044ac 100644 --- a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -13,15 +14,15 @@ namespace Tgstation.Server.Client.Components sealed class ChatBotsClient : PaginatedClient, IChatBotsClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public ChatBotsClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 364095c035..d0758f41be 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -14,15 +15,15 @@ namespace Tgstation.Server.Client.Components sealed class ConfigurationClient : PaginatedClient, IConfigurationClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public ConfigurationClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index 0acd56fca3..28b1b56b4e 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -12,20 +13,20 @@ namespace Tgstation.Server.Client.Components sealed class DreamDaemonClient : IDreamDaemonClient { /// - /// The for the + /// The for the . /// readonly IApiClient apiClient; /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public DreamDaemonClient(IApiClient apiClient, Instance instance) { this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); diff --git a/src/Tgstation.Server.Client/Components/DreamMakerClient.cs b/src/Tgstation.Server.Client/Components/DreamMakerClient.cs index 251a3b8ab6..11bacc51db 100644 --- a/src/Tgstation.Server.Client/Components/DreamMakerClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamMakerClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -13,15 +14,15 @@ namespace Tgstation.Server.Client.Components sealed class DreamMakerClient : PaginatedClient, IDreamMakerClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public DreamMakerClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index 2dbf33ab9d..55e8ee1561 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -1,39 +1,40 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client.Components { /// - /// For managing the installation + /// For managing the installation. /// public interface IByondClient { /// - /// Get the active information + /// Get the active information. /// - /// The for the operation - /// A resulting in the active information + /// The for the operation. + /// A resulting in the active information. Task ActiveVersion(CancellationToken cancellationToken); /// - /// Get all installed s + /// Get all installed s. /// /// The optional for the operation. - /// The for the operation - /// A resulting in an of installed s + /// The for the operation. + /// A resulting in an of installed s. Task> InstalledVersions(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Updates the information + /// Updates the information. /// /// The . /// The for the .zip file if is . - /// The for the operation - /// A resulting in the updated information + /// The for the operation. + /// A resulting in the updated information. Task SetActiveVersion(ByondVersionRequest installRequest, Stream zipFileStream, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IChatBotsClient.cs b/src/Tgstation.Server.Client/Components/IChatBotsClient.cs index f38bd310d5..664dcb14ac 100644 --- a/src/Tgstation.Server.Client/Components/IChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/IChatBotsClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -8,7 +9,7 @@ using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client.Components { /// - /// For managing the chat bots + /// For managing the chat bots. /// public interface IChatBotsClient { @@ -16,7 +17,7 @@ namespace Tgstation.Server.Client.Components /// List the chat bots. /// /// The optional for the operation. - /// The for the operation + /// The for the operation. /// A resulting in a of the s. Task> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken); @@ -24,7 +25,7 @@ namespace Tgstation.Server.Client.Components /// Create a chat bot. /// /// The . - /// The for the operation + /// The for the operation. /// A resulting in the of the newly created chat bot. Task Create(ChatBotCreateRequest settings, CancellationToken cancellationToken); @@ -32,7 +33,7 @@ namespace Tgstation.Server.Client.Components /// Updates a chat bot's setttings. /// /// The . - /// The for the operation + /// The for the operation. /// A resulting in the updated chat bot's . Task Update(ChatBotUpdateRequest settings, CancellationToken cancellationToken); @@ -40,7 +41,7 @@ namespace Tgstation.Server.Client.Components /// Get a specific chat bot's settings. /// /// The of the chat bot to get. - /// The for the operation + /// The for the operation. /// A resulting in the . Task GetId(EntityId settingsId, CancellationToken cancellationToken); @@ -48,8 +49,8 @@ namespace Tgstation.Server.Client.Components /// Delete a chat bot. /// /// The of the chat bot to delete. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Delete(EntityId settingsId, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index 4813807330..07199796b1 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -15,11 +16,11 @@ namespace Tgstation.Server.Client.Components public interface IConfigurationClient { /// - /// List configuration files + /// List configuration files. /// /// The optional for the operation. - /// The path to the directory to list files in - /// The for the operation + /// The path to the directory to list files in. + /// The for the operation. /// A of s in the . Task> List( PaginationSettings? paginationSettings, @@ -29,8 +30,8 @@ namespace Tgstation.Server.Client.Components /// /// Read a . /// - /// The file to read - /// The for the operation + /// The file to read. + /// The for the operation. /// A resulting in a containing the and downloaded . Task> Read(IConfigurationFile file, CancellationToken cancellationToken); @@ -39,24 +40,24 @@ namespace Tgstation.Server.Client.Components /// /// The . /// The of uploaded data. If , a delete will be attempted. - /// The for the operation + /// The for the operation. /// A resulting in the new . Task Write(ConfigurationFileRequest file, Stream uploadStream, CancellationToken cancellationToken); /// - /// Delete an empty + /// Delete an empty . /// - /// The representing the directory to delete - /// The for the operation - /// A representing the running operation + /// The representing the directory to delete. + /// The for the operation. + /// A representing the running operation. Task DeleteEmptyDirectory(IConfigurationFile directory, CancellationToken cancellationToken); /// - /// Creates an empty + /// Creates an empty . /// - /// The representing the directory to create - /// The for the operation - /// A resulting in the new + /// The representing the directory to create. + /// The for the operation. + /// A resulting in the new . Task CreateDirectory(IConfigurationFile directory, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index fb67e9c709..f636e4dc8f 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -1,49 +1,50 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client.Components { /// - /// For managing + /// For managing . /// public interface IDreamDaemonClient { /// - /// Get the represented by the + /// Get the represented by the . /// - /// The for the operation - /// A resulting in the information + /// The for the operation. + /// A resulting in the information. Task Read(CancellationToken cancellationToken); /// - /// Start + /// Start . /// - /// The for the operation - /// A resulting in the of the running operation + /// The for the operation. + /// A resulting in the of the running operation. Task Start(CancellationToken cancellationToken); /// - /// Restart + /// Restart . /// - /// The for the operation - /// A resulting in the of the running operation + /// The for the operation. + /// A resulting in the of the running operation. Task Restart(CancellationToken cancellationToken); /// - /// Shutdown + /// Shutdown . /// - /// The for the operation - /// A resulting in the information + /// The for the operation. + /// A resulting in the information. Task Shutdown(CancellationToken cancellationToken); /// - /// Update . This may trigger a + /// Update . This may trigger a . /// /// The . - /// The for the operation - /// A resulting in the information + /// The for the operation. + /// A resulting in the information. Task Update(DreamDaemonRequest dreamDaemon, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs index ccffd7a11d..0c31b60240 100644 --- a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -8,45 +9,45 @@ using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client.Components { /// - /// For managing the compiler + /// For managing the compiler. /// public interface IDreamMakerClient { /// /// Get the . /// - /// The for the operation + /// The for the operation. /// A resulting in the . Task Read(CancellationToken cancellationToken); /// /// Updates the . /// - /// The to update - /// The for the operation - /// A representing the running operation + /// The to update. + /// The for the operation. + /// A representing the running operation. Task Update(DreamMakerRequest dreamMaker, CancellationToken cancellationToken); /// - /// Compile the current repository revision + /// Compile the current repository revision. /// - /// The for the operation - /// A resulting in the for the compile + /// The for the operation. + /// A resulting in the for the compile. Task Compile(CancellationToken cancellationToken); /// /// Gets the s for the instance. /// /// The optional for the operation. - /// The for the operation + /// The for the operation. /// A resulting in a of s. Task> ListCompileJobs(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Get a + /// Get a . /// - /// The 's to get - /// The for the operation + /// The 's to get. + /// The for the operation. /// A resulting in the . Task GetCompileJob(EntityId compileJob, CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 0e0b503f28..564e089e20 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Client.Components { @@ -8,27 +8,27 @@ namespace Tgstation.Server.Client.Components public interface IInstanceClient { /// - /// The used to create the + /// The used to create the . /// Instance Metadata { get; } /// - /// Access the + /// Access the . /// IByondClient Byond { get; } /// - /// Access the + /// Access the . /// IRepositoryClient Repository { get; } /// - /// Access the + /// Access the . /// IDreamDaemonClient DreamDaemon { get; } /// - /// Access the + /// Access the . /// IConfigurationClient Configuration { get; } @@ -38,17 +38,17 @@ namespace Tgstation.Server.Client.Components IInstancePermissionSetClient PermissionSets { get; } /// - /// Access the + /// Access the . /// IChatBotsClient ChatBots { get; } /// - /// Access the + /// Access the . /// IDreamMakerClient DreamMaker { get; } /// - /// Access the + /// Access the . /// IJobsClient Jobs { get; } } diff --git a/src/Tgstation.Server.Client/Components/IInstancePermissionSetClient.cs b/src/Tgstation.Server.Client/Components/IInstancePermissionSetClient.cs index 17febd87fd..2f7d1b73a1 100644 --- a/src/Tgstation.Server.Client/Components/IInstancePermissionSetClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstancePermissionSetClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -15,48 +16,48 @@ namespace Tgstation.Server.Client.Components /// /// Get the instance permission sets associated with the logged on user. /// - /// The for the operation - /// A resulting in the associated with the logged on user + /// The for the operation. + /// A resulting in the associated with the logged on user. Task Read(CancellationToken cancellationToken); /// - /// Get a specific + /// Get a specific . /// /// The . - /// The for the operation - /// A resulting in the requested + /// The for the operation. + /// A resulting in the requested . Task GetId(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken); /// /// Get the instance permission sets in the instance. /// /// The optional for the operation. - /// The for the operation + /// The for the operation. /// A resulting in a of s in the instance. Task> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Update a + /// Update a . /// /// The . - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Update(InstancePermissionSetRequest instancePermissionSet, CancellationToken cancellationToken); /// - /// Create a + /// Create a . /// /// The . - /// The for the operation + /// The for the operation. /// A reulting in the new . Task Create(InstancePermissionSetRequest instancePermissionSet, CancellationToken cancellationToken); /// - /// Delete a + /// Delete a . /// - /// The to delete - /// The for the operation - /// A representing the running operation + /// The to delete. + /// The for the operation. + /// A representing the running operation. Task Delete(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IJobsClient.cs b/src/Tgstation.Server.Client/Components/IJobsClient.cs index 79702efcf3..108b5aab77 100644 --- a/src/Tgstation.Server.Client/Components/IJobsClient.cs +++ b/src/Tgstation.Server.Client/Components/IJobsClient.cs @@ -1,46 +1,47 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client.Components { /// - /// Access to running jobs + /// Access to running jobs. /// public interface IJobsClient { /// - /// List the s in the + /// List the s in the . /// /// The optional for the operation. - /// The for the operation - /// A resulting in a of the s in the + /// The for the operation. + /// A resulting in a of the s in the . Task> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// List the active s in the + /// List the active s in the . /// /// The optional for the operation. - /// The for the operation - /// A resulting in a of the active s in the + /// The for the operation. + /// A resulting in a of the active s in the . Task> ListActive(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Get a + /// Get a . /// - /// The 's to get - /// The for the operation - /// A resulting in the + /// The 's to get. + /// The for the operation. + /// A resulting in the . Task GetId(EntityId job, CancellationToken cancellationToken); /// - /// Cancels a + /// Cancels a . /// - /// The to cancel - /// The for the operation - /// A representing the running operation + /// The to cancel. + /// The for the operation. + /// A representing the running operation. Task Cancel(JobResponse job, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs index 645567bb64..a8495e1efd 100644 --- a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs +++ b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs @@ -1,5 +1,6 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -13,7 +14,7 @@ namespace Tgstation.Server.Client.Components /// /// Get the repository's current status. /// - /// The for the operation + /// The for the operation. /// A resulting in the . Task Read(CancellationToken cancellationToken); @@ -21,22 +22,22 @@ namespace Tgstation.Server.Client.Components /// Update the repository. /// /// The . - /// The for the operation + /// The for the operation. /// A resulting in the . Task Update(RepositoryUpdateRequest repository, CancellationToken cancellationToken); /// - /// Clones a + /// Clones a . /// /// The . - /// The for the operation - /// A resulting in the / + /// The for the operation. + /// A resulting in the /. Task Clone(RepositoryCreateRequest repository, CancellationToken cancellationToken); /// /// Deletes the repository. /// - /// The for the operation + /// The for the operation. /// A resulting in the . Task Delete(CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Client/Components/InstanceClient.cs b/src/Tgstation.Server.Client/Components/InstanceClient.cs index fda79d9220..70024af14b 100644 --- a/src/Tgstation.Server.Client/Components/InstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/InstanceClient.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Client.Components @@ -34,10 +35,10 @@ namespace Tgstation.Server.Client.Components public IJobsClient Jobs { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// /// The used to construct component clients. - /// The value of + /// The value of . public InstanceClient(IApiClient apiClient, Instance instance) { if (apiClient == null) diff --git a/src/Tgstation.Server.Client/Components/InstancePermissionSetClient.cs b/src/Tgstation.Server.Client/Components/InstancePermissionSetClient.cs index 29005fa921..1403ac073a 100644 --- a/src/Tgstation.Server.Client/Components/InstancePermissionSetClient.cs +++ b/src/Tgstation.Server.Client/Components/InstancePermissionSetClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; @@ -14,15 +15,15 @@ namespace Tgstation.Server.Client.Components sealed class InstancePermissionSetClient : PaginatedClient, IInstancePermissionSetClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public InstancePermissionSetClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/JobsClient.cs b/src/Tgstation.Server.Client/Components/JobsClient.cs index c03bdc25af..c143576873 100644 --- a/src/Tgstation.Server.Client/Components/JobsClient.cs +++ b/src/Tgstation.Server.Client/Components/JobsClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -12,15 +13,15 @@ namespace Tgstation.Server.Client.Components sealed class JobsClient : PaginatedClient, IJobsClient { /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The value of + /// The value of . public JobsClient(IApiClient apiClient, Instance instance) : base(apiClient) { diff --git a/src/Tgstation.Server.Client/Components/RepositoryClient.cs b/src/Tgstation.Server.Client/Components/RepositoryClient.cs index 9ab222b4e7..a5e47656c5 100644 --- a/src/Tgstation.Server.Client/Components/RepositoryClient.cs +++ b/src/Tgstation.Server.Client/Components/RepositoryClient.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -12,20 +13,20 @@ namespace Tgstation.Server.Client.Components sealed class RepositoryClient : IRepositoryClient { /// - /// The for the + /// The for the . /// readonly IApiClient apiClient; /// - /// The for the + /// The for the . /// readonly Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public RepositoryClient(IApiClient apiClient, Instance instance) { this.apiClient = apiClient; diff --git a/src/Tgstation.Server.Client/ConflictException.cs b/src/Tgstation.Server.Client/ConflictException.cs index 45d2e19a3c..7e18a1515b 100644 --- a/src/Tgstation.Server.Client/ConflictException.cs +++ b/src/Tgstation.Server.Client/ConflictException.cs @@ -1,38 +1,46 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when the client performs an action that would result in data conflict + /// Occurs when the client performs an action that would result in data conflict. /// public sealed class ConflictException : ApiException { /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . public ConflictException(ErrorMessageResponse? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage) - { } + { + } /// - /// Construct a + /// Initializes a new instance of the class. /// - public ConflictException() { } + public ConflictException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public ConflictException(string message) : base(message) { } + /// The message for the . + public ConflictException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public ConflictException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public ConflictException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/HttpClientImplementation.cs b/src/Tgstation.Server.Client/HttpClientImplementation.cs index 5a449b1441..d27708df2a 100644 --- a/src/Tgstation.Server.Client/HttpClientImplementation.cs +++ b/src/Tgstation.Server.Client/HttpClientImplementation.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -16,12 +16,12 @@ namespace Tgstation.Server.Client } /// - /// The real + /// The real . /// readonly HttpClient httpClient; /// - /// Construct an + /// Initializes a new instance of the class. /// public HttpClientImplementation() { diff --git a/src/Tgstation.Server.Client/IAdministrationClient.cs b/src/Tgstation.Server.Client/IAdministrationClient.cs index 1ddfb5d890..fdc92c5dfe 100644 --- a/src/Tgstation.Server.Client/IAdministrationClient.cs +++ b/src/Tgstation.Server.Client/IAdministrationClient.cs @@ -1,45 +1,46 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// For managing server administration + /// For managing server administration. /// public interface IAdministrationClient { /// - /// Get the represented by the + /// Get the represented by the . /// - /// The for the operation - /// A resulting in the represented by the + /// The for the operation. + /// A resulting in the represented by the . Task Read(CancellationToken cancellationToken); /// - /// Updates the setttings + /// Updates the setttings. /// /// The . - /// The for the operation + /// The for the operation. /// A resulting in the echoed . Task Update(ServerUpdateRequest updateRequest, CancellationToken cancellationToken); /// - /// Restarts the TGS server + /// Restarts the TGS server. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Restart(CancellationToken cancellationToken); /// /// Lists the log files available for download. /// /// The optional for the operation. - /// The for the operation + /// The for the operation. /// A resulting in an of metadata. Task> ListLogs(PaginationSettings? paginationSettings, CancellationToken cancellationToken); @@ -47,7 +48,7 @@ namespace Tgstation.Server.Client /// Download a given . /// /// The to download. - /// The for the operation + /// The for the operation. /// A resulting a containing the downloaded and associated . Task> GetLog(LogFileResponse logFile, CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index d76772f0c6..665960f25a 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -9,88 +10,88 @@ using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Web interface for the API + /// Web interface for the API. /// interface IApiClient : IDisposable { /// - /// The the uses + /// The the uses. /// ApiHeaders Headers { get; set; } /// - /// The pointing the tgstation-server + /// The pointing the tgstation-server. /// Uri Url { get; } /// - /// The request timeout + /// The request timeout. /// TimeSpan Timeout { get; set; } /// - /// Adds a to the request pipeline + /// Adds a to the request pipeline. /// - /// The to add + /// The to add. void AddRequestLogger(IRequestLogger requestLogger); /// - /// Run an HTTP PUT request + /// Run an HTTP PUT request. /// - /// The type to of the request body - /// The type of the response body - /// The server route to make the request to - /// The request body - /// The for the operation - /// A resulting in the response body as a + /// The type to of the request body. + /// The type of the response body. + /// The server route to make the request to. + /// The request body. + /// The for the operation. + /// A resulting in the response body as a . Task Create(string route, TBody body, CancellationToken cancellationToken) where TBody : class; /// - /// Run an HTTP PUT request + /// Run an HTTP PUT request. /// - /// The type of the response body - /// The server route to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Create(string route, CancellationToken cancellationToken); /// - /// Run an HTTP GET request + /// Run an HTTP GET request. /// - /// The type of the response body - /// The server route to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Read(string route, CancellationToken cancellationToken); /// - /// Run an HTTP POST request + /// Run an HTTP POST request. /// - /// The type to of the request body - /// The type of the response body - /// The server route to make the request to - /// The request body - /// The for the operation - /// A resulting in the response body as a + /// The type to of the request body. + /// The type of the response body. + /// The server route to make the request to. + /// The request body. + /// The for the operation. + /// A resulting in the response body as a . Task Update(string route, TBody body, CancellationToken cancellationToken) where TBody : class; /// - /// Run an HTTP POST request + /// Run an HTTP POST request. /// - /// The type of the response body - /// The server route to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Update(string route, CancellationToken cancellationToken); /// - /// Run an HTTP POST request + /// Run an HTTP POST request. /// - /// The type to of the request body - /// The server route to make the request to - /// The request body - /// The for the operation - /// A representing the running operation + /// The type to of the request body. + /// The server route to make the request to. + /// The request body. + /// The for the operation. + /// A representing the running operation. Task Update(string route, TBody body, CancellationToken cancellationToken) where TBody : class; /// @@ -102,23 +103,23 @@ namespace Tgstation.Server.Client Task Patch(string route, CancellationToken cancellationToken); /// - /// Run an HTTP DELETE request + /// Run an HTTP DELETE request. /// - /// The server route to make the request to - /// The for the operation - /// A representing the running operation + /// The server route to make the request to. + /// The for the operation. + /// A representing the running operation. Task Delete(string route, CancellationToken cancellationToken); /// - /// Run an HTTP PUT request + /// Run an HTTP PUT request. /// - /// The type to of the request body - /// The type of the response body - /// The server route to make the request to - /// The request body - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type to of the request body. + /// The type of the response body. + /// The server route to make the request to. + /// The request body. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Create( string route, TBody body, @@ -127,45 +128,45 @@ namespace Tgstation.Server.Client where TBody : class; /// - /// Run an HTTP PUT request + /// Run an HTTP PUT request. /// - /// The type of the response body - /// The server route to make the request to - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Create(string route, long instanceId, CancellationToken cancellationToken); /// - /// Run an HTTP PATCH request + /// Run an HTTP PATCH request. /// - /// The type of the response body - /// The server route to make the request to - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Patch(string route, long instanceId, CancellationToken cancellationToken); /// - /// Run an HTTP GET request + /// Run an HTTP GET request. /// - /// The type of the response body - /// The server route to make the request to - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Read(string route, long instanceId, CancellationToken cancellationToken); /// - /// Run an HTTP POST request + /// Run an HTTP POST request. /// - /// The type to of the request body - /// The type of the response body - /// The server route to make the request to - /// The request body - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type to of the request body. + /// The type of the response body. + /// The server route to make the request to. + /// The request body. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Update( string route, TBody body, @@ -174,33 +175,33 @@ namespace Tgstation.Server.Client where TBody : class; /// - /// Run an HTTP DELETE request + /// Run an HTTP DELETE request. /// - /// The server route to make the request to - /// The instance to make the request to - /// The for the operation - /// A representing the running operation + /// The server route to make the request to. + /// The instance to make the request to. + /// The for the operation. + /// A representing the running operation. Task Delete(string route, long instanceId, CancellationToken cancellationToken); /// - /// Run an HTTP DELETE request + /// Run an HTTP DELETE request. /// - /// The type to of the request body - /// The server route to make the request to - /// The request body - /// The instance to make the request to - /// The for the operation - /// A representing the running operation + /// The type to of the request body. + /// The server route to make the request to. + /// The request body. + /// The instance to make the request to. + /// The for the operation. + /// A representing the running operation. Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class; /// - /// Run an HTTP DELETE request + /// Run an HTTP DELETE request. /// - /// The type of the response body - /// The server route to make the request to - /// The instance to make the request to - /// The for the operation - /// A resulting in the response body as a + /// The type of the response body. + /// The server route to make the request to. + /// The instance to make the request to. + /// The for the operation. + /// A resulting in the response body as a . Task Delete(string route, long instanceId, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/IApiClientFactory.cs b/src/Tgstation.Server.Client/IApiClientFactory.cs index d95b12ea32..8e5b7d704b 100644 --- a/src/Tgstation.Server.Client/IApiClientFactory.cs +++ b/src/Tgstation.Server.Client/IApiClientFactory.cs @@ -1,20 +1,21 @@ -using System; +using System; + using Tgstation.Server.Api; namespace Tgstation.Server.Client { /// - /// For creating s + /// For creating s. /// interface IApiClientFactory { /// - /// Create an + /// Create an . /// - /// The base - /// The for the + /// The base . + /// The for the . /// The to use to generate a new . - /// A new + /// A new . IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders); } } diff --git a/src/Tgstation.Server.Client/IHttpClient.cs b/src/Tgstation.Server.Client/IHttpClient.cs index a1e146624e..e7e5ad5325 100644 --- a/src/Tgstation.Server.Client/IHttpClient.cs +++ b/src/Tgstation.Server.Client/IHttpClient.cs @@ -6,21 +6,21 @@ using System.Threading.Tasks; namespace Tgstation.Server.Client { /// - /// For sending HTTP requests + /// For sending HTTP requests. /// interface IHttpClient : IDisposable { /// - /// The request timeout + /// The request timeout. /// TimeSpan Timeout { get; set; } /// - /// Send an HTTP request + /// Send an HTTP request. /// - /// The - /// The for the operation - /// A resulting in the of the request + /// The . + /// The for the operation. + /// A resulting in the of the request. Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index 644a311840..7bf96e0ec7 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -9,48 +10,48 @@ using Tgstation.Server.Client.Components; namespace Tgstation.Server.Client { /// - /// For managing s + /// For managing s. /// public interface IInstanceManagerClient { /// - /// Get all s for s the user can view + /// Get all s for s the user can view. /// /// The optional for the operation. - /// The for the operation - /// A resulting in a of all s the user can view + /// The for the operation. + /// A resulting in a of all s the user can view. Task> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Create or attach an + /// Create or attach an . /// /// The . - /// The for the operation - /// A resulting in the created or attached + /// The for the operation. + /// A resulting in the created or attached . Task CreateOrAttach(InstanceCreateRequest instance, CancellationToken cancellationToken); /// - /// Relocates, renamed, and/or on/offlines an + /// Relocates, renamed, and/or on/offlines an . /// /// The . - /// The for the operation - /// A resulting in the updated + /// The for the operation. + /// A resulting in the updated . Task Update(InstanceUpdateRequest instance, CancellationToken cancellationToken); /// - /// Get a specific + /// Get a specific . /// - /// The to get - /// The for the operation - /// A resulting in the + /// The to get. + /// The for the operation. + /// A resulting in the . Task GetId(EntityId instance, CancellationToken cancellationToken); /// - /// Deletes an + /// Deletes an . /// - /// The of the to delete - /// The for the operation - /// A representing the running operation + /// The of the to delete. + /// The for the operation. + /// A representing the running operation. Task Detach(EntityId instance, CancellationToken cancellationToken); /// @@ -62,10 +63,10 @@ namespace Tgstation.Server.Client Task GrantPermissions(EntityId instance, CancellationToken cancellationToken); /// - /// Create an for a given + /// Create an for a given . /// - /// The of the to create an for - /// A new + /// The of the to create an for. + /// A new . IInstanceClient CreateClient(Instance instance); } } diff --git a/src/Tgstation.Server.Client/IRequestLogger.cs b/src/Tgstation.Server.Client/IRequestLogger.cs index 2a93cfa1fd..2c9fbab584 100644 --- a/src/Tgstation.Server.Client/IRequestLogger.cs +++ b/src/Tgstation.Server.Client/IRequestLogger.cs @@ -5,24 +5,24 @@ using System.Threading.Tasks; namespace Tgstation.Server.Client { /// - /// For logging HTTP requests and responses + /// For logging HTTP requests and responses. /// public interface IRequestLogger { /// - /// Log a request + /// Log a request. /// - /// The representing the request - /// The for the operation - /// A representing the running operation + /// The representing the request. + /// The for the operation. + /// A representing the running operation. Task LogRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken); /// - /// Log a response + /// Log a response. /// - /// The representing the request - /// The for the operation - /// A representing the running operation + /// The representing the request. + /// The for the operation. + /// A representing the running operation. Task LogResponse(HttpResponseMessage responseMessage, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 9939530351..6738ca5eb4 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -1,42 +1,43 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Main client for communicating with a server + /// Main client for communicating with a server. /// public interface IServerClient : IDisposable { /// - /// The connected server + /// The connected server . /// Uri Url { get; } /// - /// The used to access the server + /// The used to access the server. /// TokenResponse Token { get; set; } /// - /// The connection timeout + /// The connection timeout. /// TimeSpan Timeout { get; set; } /// - /// Access the + /// Access the . /// IInstanceManagerClient Instances { get; } /// - /// Access the + /// Access the . /// IAdministrationClient Administration { get; } /// - /// Access the + /// Access the . /// IUsersClient Users { get; } @@ -46,16 +47,16 @@ namespace Tgstation.Server.Client IUserGroupsClient Groups { get; } /// - /// The of the + /// The of the . /// - /// The for the operation - /// A resulting in the of the target server + /// The for the operation. + /// A resulting in the of the target server. Task ServerInformation(CancellationToken cancellationToken); /// - /// Adds a to the request pipeline + /// Adds a to the request pipeline. /// - /// The to add + /// The to add. void AddRequestLogger(IRequestLogger requestLogger); } } diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs index cbbbc60f63..0216fa0e4e 100644 --- a/src/Tgstation.Server.Client/IServerClientFactory.cs +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -1,27 +1,28 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Factory for creating s + /// Factory for creating s. /// public interface IServerClientFactory { /// - /// Create a + /// Create a . /// - /// The URL to access TGS - /// The username to for the - /// The password for the + /// The URL to access TGS. + /// The username to for the . + /// The password for the . /// Optional initial s to add to the . - /// Optional representing timeout for the connection + /// Optional representing timeout for the connection. /// Attempt to refresh the received when it expires or becomes invalid. and will be stored in memory if this is . - /// Optional for the operation - /// A resulting in a new + /// Optional for the operation. + /// A resulting in a new . Task CreateFromLogin( Uri host, string username, @@ -32,11 +33,11 @@ namespace Tgstation.Server.Client CancellationToken cancellationToken = default); /// - /// Create a + /// Create a . /// - /// The URL to access TGS - /// The to access the API with - /// A new + /// The URL to access TGS. + /// The to access the API with. + /// A new . IServerClient CreateFromToken( Uri host, TokenResponse token); diff --git a/src/Tgstation.Server.Client/IUserGroupsClient.cs b/src/Tgstation.Server.Client/IUserGroupsClient.cs index d2c360eb30..e9cb571b9d 100644 --- a/src/Tgstation.Server.Client/IUserGroupsClient.cs +++ b/src/Tgstation.Server.Client/IUserGroupsClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; diff --git a/src/Tgstation.Server.Client/IUsersClient.cs b/src/Tgstation.Server.Client/IUsersClient.cs index fdc8a54b81..27847be60e 100644 --- a/src/Tgstation.Server.Client/IUsersClient.cs +++ b/src/Tgstation.Server.Client/IUsersClient.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -8,47 +9,47 @@ using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// For managing s + /// For managing s. /// public interface IUsersClient { /// - /// Read the current user's information and general rights + /// Read the current user's information and general rights. /// - /// The for the operation - /// A resulting in the current + /// The for the operation. + /// A resulting in the current . Task Read(CancellationToken cancellationToken); /// - /// Get a specific + /// Get a specific . /// /// The of the to get. - /// The for the operation - /// A resulting in the requested + /// The for the operation. + /// A resulting in the requested . Task GetId(EntityId user, CancellationToken cancellationToken); /// - /// List all s + /// List all s. /// /// The optional for the operation. - /// The for the operation - /// A resulting in a of all s + /// The for the operation. + /// A resulting in a of all s. Task> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken); /// - /// Create a new + /// Create a new . /// /// The . - /// The for the operation - /// The new + /// The for the operation. + /// The new . Task Create(UserCreateRequest user, CancellationToken cancellationToken); /// - /// Update a + /// Update a . /// - /// The used to update the - /// The for the operation - /// The updated + /// The used to update the . + /// The for the operation. + /// The updated . Task Update(UserUpdateRequest user, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs index 089cd3bcb7..96179f0154 100644 --- a/src/Tgstation.Server.Client/InstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -14,12 +15,13 @@ namespace Tgstation.Server.Client sealed class InstanceManagerClient : PaginatedClient, IInstanceManagerClient { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . public InstanceManagerClient(IApiClient apiClient) : base(apiClient) - { } + { + } /// public Task CreateOrAttach(InstanceCreateRequest instance, CancellationToken cancellationToken) => ApiClient.Create(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken); diff --git a/src/Tgstation.Server.Client/InsufficientPermissionsException.cs b/src/Tgstation.Server.Client/InsufficientPermissionsException.cs index cb0b11f410..76f7259ea4 100644 --- a/src/Tgstation.Server.Client/InsufficientPermissionsException.cs +++ b/src/Tgstation.Server.Client/InsufficientPermissionsException.cs @@ -1,38 +1,45 @@ -using System; +using System; using System.Net.Http; namespace Tgstation.Server.Client { /// - /// Occurs when the client attempts to perform an action they do not have the rights for + /// Occurs when the client attempts to perform an action they do not have the rights for. /// public sealed class InsufficientPermissionsException : ApiException { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . public InsufficientPermissionsException(HttpResponseMessage responseMessage) : base( responseMessage, "The current user has insufficient permissions to perform the requested operation!") - { } + { + } /// - /// Intializes a new instance of the . + /// Initializes a new instance of the class. /// - public InsufficientPermissionsException() { } + public InsufficientPermissionsException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public InsufficientPermissionsException(string message) : base(message) { } + /// The message for the . + public InsufficientPermissionsException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public InsufficientPermissionsException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public InsufficientPermissionsException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/MethodNotSupportedException.cs b/src/Tgstation.Server.Client/MethodNotSupportedException.cs index f19dcdab0e..88a708bba4 100644 --- a/src/Tgstation.Server.Client/MethodNotSupportedException.cs +++ b/src/Tgstation.Server.Client/MethodNotSupportedException.cs @@ -1,38 +1,46 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when the client tries to use a currently unsupported API + /// Occurs when the client tries to use a currently unsupported API. /// public sealed class MethodNotSupportedException : ApiException { /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . public MethodNotSupportedException(ErrorMessageResponse? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage) - { } + { + } /// - /// Intializes a new instance of the . + /// Initializes a new instance of the class. /// - public MethodNotSupportedException() { } + public MethodNotSupportedException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public MethodNotSupportedException(string message) : base(message) { } + /// The message for the . + public MethodNotSupportedException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/PaginatedClient.cs b/src/Tgstation.Server.Client/PaginatedClient.cs index df8f023d36..5da0b248ab 100644 --- a/src/Tgstation.Server.Client/PaginatedClient.cs +++ b/src/Tgstation.Server.Client/PaginatedClient.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -15,12 +16,12 @@ namespace Tgstation.Server.Client abstract class PaginatedClient { /// - /// The for the + /// The for the . /// protected IApiClient ApiClient { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public PaginatedClient(IApiClient apiClient) @@ -65,7 +66,7 @@ namespace Tgstation.Server.Client if (paginationSettings.Offset.HasValue) { - if(paginationSettings.Offset.Value < 0) + if (paginationSettings.Offset.Value < 0) throw new ArgumentOutOfRangeException(nameof(paginationSettings), "Offset cannot be less than 0!"); pageSize ??= paginationSettings.Offset.Value; @@ -94,7 +95,7 @@ namespace Tgstation.Server.Client do { // check if first page - if(currentPage > 1) + if (currentPage > 1) currentResults = await GetPage().ConfigureAwait(false); if (currentResults.Content == null) diff --git a/src/Tgstation.Server.Client/PaginationSettings.cs b/src/Tgstation.Server.Client/PaginationSettings.cs index 8c03734f20..74b299d983 100644 --- a/src/Tgstation.Server.Client/PaginationSettings.cs +++ b/src/Tgstation.Server.Client/PaginationSettings.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Client +namespace Tgstation.Server.Client { /// /// Settings for a paginated request. diff --git a/src/Tgstation.Server.Client/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Client/Properties/AssemblyInfo.cs index 7f876c9048..44d5b32c83 100644 --- a/src/Tgstation.Server.Client/Properties/AssemblyInfo.cs +++ b/src/Tgstation.Server.Client/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Tgstation.Server.Tests")] diff --git a/src/Tgstation.Server.Client/RateLimitException.cs b/src/Tgstation.Server.Client/RateLimitException.cs index 39df1df3ad..e4caf6bb9c 100644 --- a/src/Tgstation.Server.Client/RateLimitException.cs +++ b/src/Tgstation.Server.Client/RateLimitException.cs @@ -1,13 +1,15 @@ -using Microsoft.Net.Http.Headers; -using System; +using System; using System.Linq; using System.Net.Http; + +using Microsoft.Net.Http.Headers; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when a GitHub rate limit occurs + /// Occurs when a GitHub rate limit occurs. /// public sealed class RateLimitException : ApiException { @@ -17,7 +19,7 @@ namespace Tgstation.Server.Client public DateTimeOffset? RetryAfter { get; } /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -35,21 +37,27 @@ namespace Tgstation.Server.Client } /// - /// Construct an + /// Initializes a new instance of the class. /// - public RateLimitException() { } + public RateLimitException() + { + } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The message for the . - public RateLimitException(string message) : base(message) { } + public RateLimitException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public RateLimitException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public RateLimitException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/RequestTimeoutException.cs b/src/Tgstation.Server.Client/RequestTimeoutException.cs index 5acbcd633f..7d24457bd5 100644 --- a/src/Tgstation.Server.Client/RequestTimeoutException.cs +++ b/src/Tgstation.Server.Client/RequestTimeoutException.cs @@ -1,36 +1,43 @@ -using System; +using System; using System.Net.Http; namespace Tgstation.Server.Client { /// - /// Occurs when the client provides invalid credentials + /// Occurs when the client provides invalid credentials. /// public sealed class RequestTimeoutException : ClientException { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . public RequestTimeoutException(HttpResponseMessage responseMessage) : base(responseMessage, "The request timed out!") - { } + { + } /// - /// Intializes a new instance of the . + /// Initializes a new instance of the class. /// - public RequestTimeoutException() { } + public RequestTimeoutException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public RequestTimeoutException(string message) : base(message) { } + /// The message for the . + public RequestTimeoutException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public RequestTimeoutException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public RequestTimeoutException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/ServerClient.cs b/src/Tgstation.Server.Client/ServerClient.cs index 283519241f..f0d73b7eab 100644 --- a/src/Tgstation.Server.Client/ServerClient.cs +++ b/src/Tgstation.Server.Client/ServerClient.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models.Response; @@ -43,20 +44,20 @@ namespace Tgstation.Server.Client public IUserGroupsClient Groups { get; } /// - /// The for the + /// The for the . /// readonly IApiClient apiClient; /// - /// Backing field for + /// Backing field for . /// TokenResponse token; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public ServerClient(IApiClient apiClient, TokenResponse token) { this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs index 4cd28528fa..4acd77d8ee 100644 --- a/src/Tgstation.Server.Client/ServerClientFactory.cs +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models.Response; @@ -13,19 +14,19 @@ namespace Tgstation.Server.Client public sealed class ServerClientFactory : IServerClientFactory { /// - /// The for the + /// The for the . /// static readonly IApiClientFactory ApiClientFactory = new ApiClientFactory(); /// - /// The for the + /// The for the . /// readonly ProductHeaderValue productHeaderValue; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public ServerClientFactory(ProductHeaderValue productHeaderValue) { this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue)); diff --git a/src/Tgstation.Server.Client/ServerErrorException.cs b/src/Tgstation.Server.Client/ServerErrorException.cs index ea6bb98357..805b797440 100644 --- a/src/Tgstation.Server.Client/ServerErrorException.cs +++ b/src/Tgstation.Server.Client/ServerErrorException.cs @@ -1,39 +1,46 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when an error occurs in the server + /// Occurs when an error occurs in the server. /// public sealed class ServerErrorException : ApiException { /// - /// Construct an + /// Initializes a new instance of the class. /// - public ServerErrorException() { } + public ServerErrorException() + { + } /// - /// Construct an with a given + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . public ServerErrorException(ErrorMessageResponse? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage) { } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The message for the . - public ServerErrorException(string message) : base(message) { } + public ServerErrorException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public ServerErrorException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public ServerErrorException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/ServiceUnavailableException.cs b/src/Tgstation.Server.Client/ServiceUnavailableException.cs index e471416c9e..fb2ae62c28 100644 --- a/src/Tgstation.Server.Client/ServiceUnavailableException.cs +++ b/src/Tgstation.Server.Client/ServiceUnavailableException.cs @@ -1,36 +1,43 @@ -using System; +using System; using System.Net.Http; namespace Tgstation.Server.Client { /// - /// Occurs when the client makes a request while the server is starting or stopping + /// Occurs when the client makes a request while the server is starting or stopping. /// public sealed class ServiceUnavailableException : ClientException { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The . public ServiceUnavailableException(HttpResponseMessage responseMessage) : base(responseMessage, "The service is unavailable!") - { } + { + } /// - /// Intializes a new instance of the . + /// Initializes a new instance of the class. /// - public ServiceUnavailableException() { } + public ServiceUnavailableException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public ServiceUnavailableException(string message) : base(message) { } + /// The message for the . + public ServiceUnavailableException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public ServiceUnavailableException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public ServiceUnavailableException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index ccf32e9b71..cdaaf000b2 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -36,6 +36,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Client/UnauthorizedException.cs b/src/Tgstation.Server.Client/UnauthorizedException.cs index 8b1356f1c5..7a1ad6753b 100644 --- a/src/Tgstation.Server.Client/UnauthorizedException.cs +++ b/src/Tgstation.Server.Client/UnauthorizedException.cs @@ -1,38 +1,46 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when the client provides invalid credentials + /// Occurs when the client provides invalid credentials. /// public sealed class UnauthorizedException : ApiException { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The returned by the API. /// The . public UnauthorizedException(ErrorMessageResponse? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage) - { } + { + } /// - /// Intializes a new instance of the . + /// Initializes a new instance of the class. /// - public UnauthorizedException() { } + public UnauthorizedException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public UnauthorizedException(string message) : base(message) { } + /// The message for the . + public UnauthorizedException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public UnauthorizedException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public UnauthorizedException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/UnrecognizedResponseException.cs b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs index 5b0577f8fd..30e09da8d0 100644 --- a/src/Tgstation.Server.Client/UnrecognizedResponseException.cs +++ b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs @@ -4,33 +4,40 @@ using System.Net.Http; namespace Tgstation.Server.Client { /// - /// Occurs when a response is received that did not deserialize to one of the expected + /// Occurs when a response is received that did not deserialize to one of the expected . /// sealed class UnrecognizedResponseException : ClientException { /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The . public UnrecognizedResponseException(HttpResponseMessage responseMessage) : base(responseMessage, "Unrecognized response body!") - { } + { + } /// - /// Construct a + /// Initializes a new instance of the class. /// - public UnrecognizedResponseException() { } + public UnrecognizedResponseException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public UnrecognizedResponseException(string message) : base(message) { } + /// The message for the . + public UnrecognizedResponseException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public UnrecognizedResponseException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public UnrecognizedResponseException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Client/UserGroupsClient.cs b/src/Tgstation.Server.Client/UserGroupsClient.cs index 85c9d799ed..449258b119 100644 --- a/src/Tgstation.Server.Client/UserGroupsClient.cs +++ b/src/Tgstation.Server.Client/UserGroupsClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -13,7 +14,7 @@ namespace Tgstation.Server.Client sealed class UserGroupsClient : PaginatedClient, IUserGroupsClient { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . public UserGroupsClient(IApiClient apiClient) diff --git a/src/Tgstation.Server.Client/UsersClient.cs b/src/Tgstation.Server.Client/UsersClient.cs index def6266506..2a7b6626f7 100644 --- a/src/Tgstation.Server.Client/UsersClient.cs +++ b/src/Tgstation.Server.Client/UsersClient.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -13,7 +14,7 @@ namespace Tgstation.Server.Client sealed class UsersClient : PaginatedClient, IUsersClient { /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the . public UsersClient(IApiClient apiClient) diff --git a/src/Tgstation.Server.Client/VersionMismatchException.cs b/src/Tgstation.Server.Client/VersionMismatchException.cs index 9fe74c47ee..e6fe4edf9c 100644 --- a/src/Tgstation.Server.Client/VersionMismatchException.cs +++ b/src/Tgstation.Server.Client/VersionMismatchException.cs @@ -1,16 +1,17 @@ -using System; +using System; using System.Net.Http; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Client { /// - /// Occurs when the API version of the client is not compatible with the server's + /// Occurs when the API version of the client is not compatible with the server's. /// public sealed class VersionMismatchException : ApiException { /// - /// Initialize a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -19,21 +20,27 @@ namespace Tgstation.Server.Client } /// - /// Construct an + /// Initializes a new instance of the class. /// - public VersionMismatchException() { } + public VersionMismatchException() + { + } /// - /// Construct an with a + /// Initializes a new instance of the class. /// - /// The message for the - public VersionMismatchException(string message) : base(message) { } + /// The message for the . + public VersionMismatchException(string message) : base(message) + { + } /// - /// Construct an with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the base - public VersionMismatchException(string message, Exception innerException) : base(message, innerException) { } + /// The message for the . + /// The inner for the base . + public VersionMismatchException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 9f3314dcaa..29f18a9a44 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,27 +1,29 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Console { /// - /// Contains the entrypoint for the application + /// Contains the entrypoint for the application. /// static class Program { /// - /// The for the + /// The for the . /// internal static IWatchdogFactory WatchdogFactory { get; set; } = new WatchdogFactory(); /// - /// Entrypoint for the application + /// Entrypoint for the application. /// - /// The arguments for the - /// A representing the running operation + /// The arguments for the . + /// A representing the running operation. internal static async Task Main(string[] args) { using var loggerFactory = new LoggerFactory(); diff --git a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs index 40d2c0f8474b4042e237e7c8162d9491ae56ea9c..7771d518bc17451ed916c9e92455d9e97d8fac4d 100644 GIT binary patch literal 649 zcmZ8e%Z}4P5WMFrO1=P+*DHnFN*q=TD}fZWQi4_pap;UY_7Hn!G~EvJw{hT;_yu~r z39uaIsqU)ks$V~TejyPa_rrXH?JUY;^VU-rqNsP$+{Kn}0}ZoyX|_{JnMF3XsNye5p4`Fw1!vgt?DR{-fQ*h>uPmG-5rT*dRel_rk2gun5lPh4GhBJCPF@m*H z?pP7?g4y{KhlLQ5*p(?S6%<;Ci|L7x5NKpc}BOxxu@mjlqVpLQcQFbhES{;#Jc kv3nz~7MniD$&xc06oJo!ncio9UF==*8!1dQ40E&k4Gh8LTL1t6 literal 1316 zcmaKsQESvt5QWdP(Eo7Bixun^^sV4SQ40!6p%oGFA+B*Zb~h=Ttg?Sz{mvaj(iBTb zZgOYl%(-V~^5^$wmFl=EZS_?f9dxFoE7neZ-d*UGYP?oz)aKnz=LeMU@4#Em`@ z+mN}{FCs2jcd+mDn%$oFgD%;#PtUHsd#&Nr6MxN{S(U^`mbY;AS!-CBQ<>jeqLW@^ z7VkkzIbWm4Sk29t3B>~I286`D&ijR)=!=f@DBq_##pb-$Cgm&}7#PFf&R&6Tv;)sU z)pSqSL{2l=!ZA)4tLycU-ewXkd^RgolWWhLt7Fu4K6T>h^LTajHuwdz$ZNmg?8>(K zfptmrVsg&QJC+kLD*fc%fZEMi)?hT5K?SbSE22|o^pWw2GljlA< f!`;x}H|^0u<^T8Eyq5=!W3C!<_+REfVf6K1KsV%m diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index f86b67243c..b21c71b5c5 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,6 +1,4 @@ -using McMaster.Extensions.CommandLineUtils; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Specialized; using System.Configuration.Install; using System.Diagnostics; @@ -11,54 +9,60 @@ using System.Security.Principal; using System.ServiceProcess; using System.Threading.Tasks; using System.Windows.Forms; + +using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service { /// - /// Contains the entrypoint for the application + /// Contains the entrypoint for the application. /// sealed class Program { /// - /// The for the + /// The for the . /// +#pragma warning disable SA1401 // Fields should be private internal static IWatchdogFactory WatchdogFactory = new WatchdogFactory(); +#pragma warning restore SA1401 // Fields should be private /// - /// The --uninstall or -u option + /// The --uninstall or -u option. /// [Option(ShortName = "u")] public bool Uninstall { get; } /// - /// The --install or -i option + /// The --install or -i option. /// [Option(ShortName = "i")] public bool Install { get; set; } /// - /// The --configure or -c option + /// The --configure or -c option. /// [Option(ShortName = "c")] public bool Configure { get; set; } /// - /// The --trace or -t option. Enables trace logs + /// The --trace or -t option. Enables trace logs. /// [Option(ShortName = "t")] public bool Trace { get; set; } /// - /// The --debug or -d option. Enables debug logs + /// The --debug or -d option. Enables debug logs. /// [Option(ShortName = "d")] public bool Debug { get; set; } /// - /// Check if the running user is a system administrator + /// Check if the running user is a system administrator. /// - /// if the running user is a system administrator, otherwise + /// if the running user is a system administrator, otherwise. static bool IsAdministrator() { var user = WindowsIdentity.GetCurrent(); @@ -67,14 +71,16 @@ namespace Tgstation.Server.Host.Service } /// - /// Entrypoint for the application + /// Entrypoint for the application. /// + /// The application arguments. + /// A resulting in the 's exit code. static Task Main(string[] args) => CommandLineApplication.ExecuteAsync(args); /// - /// Command line handler, always runs + /// Command line handler, always runs. /// - /// A representing the running operation + /// A representing the running operation. public async Task OnExecuteAsync() { if (Environment.UserInteractive) diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs index d7c3dd8a1b..3f2c7f1de8 100644 --- a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs +++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] +[assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index f94ed5fdcb..17212c9596 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -1,46 +1,48 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.EventLog; -using System; +using System; using System.Diagnostics; using System.Globalization; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.EventLog; + using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service { /// - /// Represents a as a + /// Represents a as a . /// sealed class ServerService : ServiceBase { /// - /// The canonical windows service name + /// The canonical windows service name. /// public const string Name = "tgstation-server-4"; /// - /// The for the + /// The for the . /// readonly IWatchdog watchdog; /// - /// The recieved from of + /// The recieved from of . /// Task watchdogTask; /// - /// The for the + /// The for the . /// CancellationTokenSource cancellationTokenSource; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The to create with - /// The for - /// The minimum to record in the event log + /// The to create with. + /// The for . + /// The minimum to record in the event log. public ServerService(IWatchdogFactory watchdogFactory, ILoggerFactory loggerFactory, LogLevel minumumLogLevel) { if (watchdogFactory == null) @@ -54,7 +56,7 @@ namespace Tgstation.Server.Host.Service LogName = EventLog.Log, MachineName = EventLog.MachineName, SourceName = EventLog.Source, - Filter = (message, logLevel) => logLevel >= minumumLogLevel + Filter = (message, logLevel) => logLevel >= minumumLogLevel, }); #pragma warning restore CS0618 // Type or member is obsolete @@ -62,32 +64,6 @@ namespace Tgstation.Server.Host.Service watchdog = watchdogFactory.CreateWatchdog(loggerFactory); } - /// - /// Executes the , stopping the service if it exits - /// - /// The arguments for the - /// The for the operation - /// A representing the running operation - async Task RunWatchdog(string[] args, CancellationToken cancellationToken) - { - await watchdog.RunAsync(false, args, cancellationTokenSource.Token).ConfigureAwait(false); - - void StopServiceAsync() - { - try - { - Task.Run(Stop, cancellationToken); - } - catch (OperationCanceledException) { } - catch (Exception e) - { - EventLog.WriteEntry(String.Format(CultureInfo.InvariantCulture, "Error stopping service! Exception: {0}", e)); - } - } - - StopServiceAsync(); - } - /// protected override void Dispose(bool disposing) { @@ -109,5 +85,33 @@ namespace Tgstation.Server.Host.Service cancellationTokenSource.Cancel(); watchdogTask.GetAwaiter().GetResult(); } + + /// + /// Executes the , stopping the service if it exits. + /// + /// The arguments for the . + /// The for the operation. + /// A representing the running operation. + async Task RunWatchdog(string[] args, CancellationToken cancellationToken) + { + await watchdog.RunAsync(false, args, cancellationTokenSource.Token).ConfigureAwait(false); + + void StopServiceAsync() + { + try + { + Task.Run(Stop, cancellationToken); + } + catch (OperationCanceledException) + { + } + catch (Exception e) + { + EventLog.WriteEntry(String.Format(CultureInfo.InvariantCulture, "Error stopping service! Exception: {0}", e)); + } + } + + StopServiceAsync(); + } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 106d25a2e9..4496a5fb0a 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -31,6 +31,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs index 81333440d6..cb587dfddc 100644 --- a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs @@ -4,17 +4,17 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Watchdog { /// - /// The watchdog for a + /// The watchdog for a . /// public interface IWatchdog { /// - /// Run the + /// Run the . /// - /// If the should just run the host configuration wizard and exit - /// The arguments for the - /// The for the operation - /// A representing the running operation + /// If the should just run the host configuration wizard and exit. + /// The arguments for the . + /// The for the operation. + /// A representing the running operation. Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs index 9534709a2f..c4dfe2bd00 100644 --- a/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs @@ -3,15 +3,15 @@ namespace Tgstation.Server.Host.Watchdog { /// - /// Factory for creating s + /// Factory for creating s. /// public interface IWatchdogFactory { /// - /// Create a + /// Create a . /// - /// The to use for error reporting - /// A new + /// The to use for error reporting. + /// A new . IWatchdog CreateWatchdog(ILoggerFactory loggerFactory); } } diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index b1f2e01dee..40777997cc 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -30,6 +30,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index d9ab43cfb6..233b273375 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -10,54 +9,30 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + namespace Tgstation.Server.Host.Watchdog { /// sealed class Watchdog : IWatchdog { /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public Watchdog(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - string GetDotnetPath(bool isWindows) - { - var enviromentPath = Environment.GetEnvironmentVariable("PATH"); - var paths = enviromentPath.Split(';'); - - var exeName = "dotnet"; - IEnumerable enumerator; - if (isWindows) - { - exeName += ".exe"; - enumerator = paths; - } - else - enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); - - enumerator = enumerator.Select(x => Path.Combine(x, exeName)); - - return enumerator - .Where(x => - { - logger.LogTrace("Checking for dotnet at {0}", x); - return File.Exists(x); - }) - .FirstOrDefault(); - } - /// - #pragma warning disable CA1502 // TODO: Decomplexify - #pragma warning disable CA1506 +#pragma warning disable CA1502 // TODO: Decomplexify +#pragma warning disable CA1506 public async Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken) { logger.LogInformation("Host watchdog starting..."); @@ -135,7 +110,7 @@ namespace Tgstation.Server.Host.Watchdog { $"\"{assemblyPath}\"", $"\"{updateDirectory}\"", - $"\"{watchdogVersion}\"" + $"\"{watchdogVersion}\"", }; if (args.Any(x => x.Equals("--attach-host-debugger", StringComparison.OrdinalIgnoreCase))) @@ -185,11 +160,16 @@ namespace Tgstation.Server.Host.Watchdog { processCts.CancelAfter(TimeSpan.FromSeconds(10)); } - catch (ObjectDisposedException) { } // race conditions + catch (ObjectDisposedException) + { + // race conditions + } })) await tcs.Task.ConfigureAwait(false); } - catch (InvalidOperationException) { } + catch (InvalidOperationException) + { + } finally { try @@ -201,7 +181,10 @@ namespace Tgstation.Server.Host.Watchdog process.WaitForExit(); } } - catch (InvalidOperationException) { } + catch (InvalidOperationException) + { + } + logger.LogInformation("Host exited!"); } @@ -312,7 +295,38 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Host watchdog exiting..."); } } - #pragma warning restore CA1502 - #pragma warning restore CA1506 +#pragma warning restore CA1502 +#pragma warning restore CA1506 + + /// + /// Gets the path to the dotnet executable. + /// + /// If the current system is a Windows OS. + /// The path to the dotnet executable. + string GetDotnetPath(bool isWindows) + { + var enviromentPath = Environment.GetEnvironmentVariable("PATH"); + var paths = enviromentPath.Split(';'); + + var exeName = "dotnet"; + IEnumerable enumerator; + if (isWindows) + { + exeName += ".exe"; + enumerator = paths; + } + else + enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); + + enumerator = enumerator.Select(x => Path.Combine(x, exeName)); + + return enumerator + .Where(x => + { + logger.LogTrace("Checking for dotnet at {0}", x); + return File.Exists(x); + }) + .FirstOrDefault(); + } } } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index e3ad6602b7..083d89eafb 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,5 +1,6 @@ -using Microsoft.Extensions.Logging; -using System; +using System; + +using Microsoft.Extensions.Logging; namespace Tgstation.Server.Host.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index 21e95c4452..4247fc047f 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -35,13 +36,13 @@ namespace Tgstation.Server.Host.Components.Byond readonly string trustedFilePath; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . /// The value of . - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . /// The value of . public ByondExecutableLock( IIOManager ioManager, @@ -63,10 +64,14 @@ namespace Tgstation.Server.Host.Components.Byond // That changed at some point so these functions are intentioanlly left blank /// - public void Dispose() { } + public void Dispose() + { + } /// - public void DoNotDeleteThisSession() { } + public void DoNotDeleteThisSession() + { + } /// public async Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs b/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs index 2993e1df45..0c3fb51093 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -40,7 +42,7 @@ namespace Tgstation.Server.Host.Components.Byond protected ILogger Logger { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 5d97995cb6..46eef5bc71 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +6,9 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Events; @@ -20,7 +22,7 @@ namespace Tgstation.Server.Host.Components.Byond sealed class ByondManager : IByondManager { /// - /// The path to the BYOND bin folder + /// The path to the BYOND bin folder. /// public const string BinPath = "byond/bin"; @@ -35,12 +37,12 @@ namespace Tgstation.Server.Host.Components.Byond const string TrustedDmbFileName = "trusted.txt"; /// - /// The file in which we store the for installations + /// The file in which we store the for installations. /// const string VersionFileName = "Version.txt"; /// - /// The file in which we store the for the active installation + /// The file in which we store the for the active installation. /// const string ActiveVersionFileName = "ActiveVersion.txt"; @@ -58,52 +60,52 @@ namespace Tgstation.Server.Host.Components.Byond } /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IByondInstaller byondInstaller; /// - /// The for the + /// The for the . /// readonly IEventConsumer eventConsumer; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Map of byond s to s that complete when they are installed + /// Map of byond s to s that complete when they are installed. /// readonly Dictionary installedVersions; /// - /// The for the + /// The for the . /// readonly SemaphoreSlim semaphore; /// - /// Converts a BYOND to a + /// Converts a BYOND to a . /// /// The to convert. /// If the property of should be kept. - /// The representation of + /// The representation of . static string VersionKey(Version version, bool allowPatch) => (allowPatch && version.Build > 0 ? new Version(version.Major, version.Minor, version.Build) : new Version(version.Major, version.Minor)).ToString(); /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, IEventConsumer eventConsumer, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -118,125 +120,6 @@ namespace Tgstation.Server.Host.Components.Byond /// public void Dispose() => semaphore.Dispose(); - /// - /// Installs a BYOND if it isn't already - /// - /// The BYOND to install - /// Custom zip file to use. Will cause a number to be added. - /// The for the operation - /// A representing the running operation - async Task InstallVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken) - { - var ourTcs = new TaskCompletionSource(); - Task inProgressTask; - string versionKey; - bool installed; - lock (installedVersions) - { - if (customVersionStream != null) - { - int customInstallationNumber = 1; - do - { - versionKey = $"{VersionKey(version, false)}.{customInstallationNumber++}"; - } - while (installedVersions.ContainsKey(versionKey)); - } - else - versionKey = VersionKey(version, true); - - installed = installedVersions.TryGetValue(versionKey, out inProgressTask); - if (!installed) - installedVersions.Add(versionKey, ourTcs.Task); - } - - if (installed) - using (cancellationToken.Register(() => ourTcs.SetCanceled())) - { - await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - return versionKey; - } - - if (customVersionStream != null) - logger.LogInformation("Installing custom BYOND version as {0}...", versionKey); - else if (version.Build > 0) - throw new JobException(ErrorCode.ByondNonExistentCustomVersion); - else - logger.LogDebug("Requested BYOND version {0} not currently installed. Doing so now..."); - - // okay up to us to install it then - try - { - await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionKey }, cancellationToken).ConfigureAwait(false); - - var extractPath = ioManager.ResolvePath(versionKey); - async Task DirectoryCleanup() - { - await ioManager.DeleteDirectory(extractPath, cancellationToken).ConfigureAwait(false); - await ioManager.CreateDirectory(extractPath, cancellationToken).ConfigureAwait(false); - } - - var directoryCleanupTask = DirectoryCleanup(); - try - { - Stream versionZipStream; - Stream downloadedStream = null; - if (customVersionStream == null) - { - var bytes = await byondInstaller.DownloadVersion(version, cancellationToken).ConfigureAwait(false); - downloadedStream = new MemoryStream(bytes); - versionZipStream = downloadedStream; - } - else - versionZipStream = customVersionStream; - - using (downloadedStream) - { - await directoryCleanupTask.ConfigureAwait(false); - logger.LogTrace("Extracting downloaded BYOND zip to {0}...", extractPath); - await ioManager.ZipToDirectory(extractPath, versionZipStream, cancellationToken).ConfigureAwait(false); - } - - await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(false); - - // make sure to do this last because this is what tells us we have a valid version in the future - await ioManager.WriteAllBytes( - ioManager.ConcatPath(versionKey, VersionFileName), - Encoding.UTF8.GetBytes(versionKey), - cancellationToken) - .ConfigureAwait(false); - } - catch (WebException e) - { - // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException - throw new JobException(ErrorCode.ByondDownloadFail, e); - } - catch (OperationCanceledException) - { - throw; - } - catch - { - await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); - throw; - } - - ourTcs.SetResult(null); - } - catch (Exception e) - { - if (!(e is OperationCanceledException)) - await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { e.Message }, cancellationToken).ConfigureAwait(false); - lock (installedVersions) - installedVersions.Remove(versionKey); - ourTcs.SetException(e); - throw; - } - - return versionKey; - } - /// public async Task ChangeVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken) { @@ -254,7 +137,7 @@ namespace Tgstation.Server.Host.Components.Byond ActiveVersion != null ? VersionKey(ActiveVersion, true) : null, - versionKey + versionKey, }, cancellationToken) .ConfigureAwait(false); @@ -376,5 +259,124 @@ namespace Tgstation.Server.Host.Components.Byond /// public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Installs a BYOND if it isn't already. + /// + /// The BYOND to install. + /// Custom zip file to use. Will cause a number to be added. + /// The for the operation. + /// A representing the running operation. + async Task InstallVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken) + { + var ourTcs = new TaskCompletionSource(); + Task inProgressTask; + string versionKey; + bool installed; + lock (installedVersions) + { + if (customVersionStream != null) + { + int customInstallationNumber = 1; + do + { + versionKey = $"{VersionKey(version, false)}.{customInstallationNumber++}"; + } + while (installedVersions.ContainsKey(versionKey)); + } + else + versionKey = VersionKey(version, true); + + installed = installedVersions.TryGetValue(versionKey, out inProgressTask); + if (!installed) + installedVersions.Add(versionKey, ourTcs.Task); + } + + if (installed) + using (cancellationToken.Register(() => ourTcs.SetCanceled())) + { + await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + return versionKey; + } + + if (customVersionStream != null) + logger.LogInformation("Installing custom BYOND version as {0}...", versionKey); + else if (version.Build > 0) + throw new JobException(ErrorCode.ByondNonExistentCustomVersion); + else + logger.LogDebug("Requested BYOND version {0} not currently installed. Doing so now..."); + + // okay up to us to install it then + try + { + await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionKey }, cancellationToken).ConfigureAwait(false); + + var extractPath = ioManager.ResolvePath(versionKey); + async Task DirectoryCleanup() + { + await ioManager.DeleteDirectory(extractPath, cancellationToken).ConfigureAwait(false); + await ioManager.CreateDirectory(extractPath, cancellationToken).ConfigureAwait(false); + } + + var directoryCleanupTask = DirectoryCleanup(); + try + { + Stream versionZipStream; + Stream downloadedStream = null; + if (customVersionStream == null) + { + var bytes = await byondInstaller.DownloadVersion(version, cancellationToken).ConfigureAwait(false); + downloadedStream = new MemoryStream(bytes); + versionZipStream = downloadedStream; + } + else + versionZipStream = customVersionStream; + + using (downloadedStream) + { + await directoryCleanupTask.ConfigureAwait(false); + logger.LogTrace("Extracting downloaded BYOND zip to {0}...", extractPath); + await ioManager.ZipToDirectory(extractPath, versionZipStream, cancellationToken).ConfigureAwait(false); + } + + await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(false); + + // make sure to do this last because this is what tells us we have a valid version in the future + await ioManager.WriteAllBytes( + ioManager.ConcatPath(versionKey, VersionFileName), + Encoding.UTF8.GetBytes(versionKey), + cancellationToken) + .ConfigureAwait(false); + } + catch (WebException e) + { + // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException + throw new JobException(ErrorCode.ByondDownloadFail, e); + } + catch (OperationCanceledException) + { + throw; + } + catch + { + await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); + throw; + } + + ourTcs.SetResult(null); + } + catch (Exception e) + { + if (!(e is OperationCanceledException)) + await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { e.Message }, cancellationToken).ConfigureAwait(false); + lock (installedVersions) + installedVersions.Remove(versionKey); + ourTcs.SetException(e); + throw; + } + + return versionKey; + } } } diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs index 42a42e732e..200aca5a71 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs @@ -5,27 +5,27 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Byond { /// - /// Represents usage of the two primary BYOND server executables + /// Represents usage of the two primary BYOND server executables. /// public interface IByondExecutableLock : IDisposable { /// - /// The of the locked executables + /// The of the locked executables. /// Version Version { get; } /// - /// The path to the DreamDaemon executable + /// The path to the DreamDaemon executable. /// string DreamDaemonPath { get; } /// - /// The path to the dm/DreamMaker executable + /// The path to the dm/DreamMaker executable. /// string DreamMakerPath { get; } /// - /// Call if, during a detach, this version should not be deleted + /// Call if, during a detach, this version should not be deleted. /// void DoNotDeleteThisSession(); diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs index 5bea0e0744..398796c482 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs @@ -5,17 +5,17 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Byond { /// - /// For downloading and installing BYOND extractions for a given system + /// For downloading and installing BYOND extractions for a given system. /// interface IByondInstaller { /// - /// Get the file name of the DreamDaemon executable + /// Get the file name of the DreamDaemon executable. /// string DreamDaemonName { get; } /// - /// Get the file name of the DreamMaker executable + /// Get the file name of the DreamMaker executable. /// string DreamMakerName { get; } @@ -25,27 +25,27 @@ namespace Tgstation.Server.Host.Components.Byond string PathToUserByondFolder { get; } /// - /// Download a given BYOND + /// Download a given BYOND . /// - /// The of BYOND to download - /// The for the operation - /// A resulting in the s of the zipfile + /// The of BYOND to download. + /// The for the operation. + /// A resulting in the s of the zipfile. Task DownloadVersion(Version version, CancellationToken cancellationToken); /// - /// Does actions necessary to get an extracted BYOND installation working + /// Does actions necessary to get an extracted BYOND installation working. /// - /// The path to the BYOND installation - /// The of BYOND being installed - /// The for the operation - /// A representing the running operation + /// The path to the BYOND installation. + /// The of BYOND being installed. + /// The for the operation. + /// A representing the running operation. Task InstallByond(string path, Version version, CancellationToken cancellationToken); /// - /// Attempts to cleans the BYOND cache folder for the system + /// Attempts to cleans the BYOND cache folder for the system. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task CleanCache(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs index 129fa54037..b38217c987 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs @@ -1,42 +1,43 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + namespace Tgstation.Server.Host.Components.Byond { /// - /// For managing the BYOND installation + /// For managing the BYOND installation. /// public interface IByondManager : IHostedService, IDisposable { /// - /// The currently active BYOND version + /// The currently active BYOND version. /// Version ActiveVersion { get; } /// - /// The installed BYOND versions + /// The installed BYOND versions. /// IReadOnlyList InstalledVersions { get; } /// - /// Change the active BYOND version + /// Change the active BYOND version. /// - /// The new + /// The new . /// Optional of a custom BYOND version zip file. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task ChangeVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken); /// - /// Lock the current installation's location and return a + /// Lock the current installation's location and return a . /// - /// The BYOND required - /// The for the operation - /// A resulting in the requested + /// The BYOND required. + /// The for the operation. + /// A resulting in the requested . Task UseExecutables(Version requiredVersion, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index fb2508a6a2..099d0ab63e 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -1,20 +1,33 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Globalization; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond { /// - /// for Posix systems + /// for Posix systems. /// sealed class PosixByondInstaller : ByondInstallerBase { + /// + /// The name of the DreamDaemon binary file. + /// const string DreamDaemonExecutableName = "DreamDaemon"; + + /// + /// The name of the DreamMaker binary file. + /// const string DreamMakerExecutableName = "DreamMaker"; + + /// + /// File extension for shell scripts. + /// const string ShellScriptExtension = ".sh"; /// @@ -30,14 +43,14 @@ namespace Tgstation.Server.Host.Components.Byond protected override string ByondRevisionsURLTemplate => "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; /// - /// The for the + /// The for the . /// readonly IPostWriteHandler postWriteHandler; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . /// The for the . /// The for the . public PosixByondInstaller(IPostWriteHandler postWriteHandler, IIOManager ioManager, ILogger logger) diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 12782bbb8b..251a65ca22 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -12,27 +14,27 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Byond { /// - /// for windows systems + /// for windows systems. /// sealed class WindowsByondInstaller : ByondInstallerBase, IDisposable { /// - /// Directory to byond installation configuration + /// Directory to byond installation configuration. /// const string ByondConfigDir = "byond/cfg"; /// - /// BYOND's DreamDaemon config file + /// BYOND's DreamDaemon config file. /// const string ByondDDConfig = "daemon.txt"; /// - /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb + /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb. /// const string ByondNoPromptTrustedMode = "trusted-check 0"; /// - /// The directory that contains the BYOND directx redistributable + /// The directory that contains the BYOND directx redistributable. /// const string ByondDXDir = "byond/directx"; @@ -49,24 +51,24 @@ namespace Tgstation.Server.Host.Components.Byond protected override string ByondRevisionsURLTemplate => "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; /// - /// The for the + /// The for the . /// readonly IProcessExecutor processExecutor; /// - /// The for the + /// The for the . /// readonly SemaphoreSlim semaphore; /// - /// If DirectX was installed + /// If DirectX was installed. /// bool installedDirectX; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . /// The for the . /// The for the . public WindowsByondInstaller(IProcessExecutor processExecutor, IIOManager ioManager, ILogger logger) @@ -90,6 +92,12 @@ namespace Tgstation.Server.Host.Components.Byond InstallDirectX(path, cancellationToken), AddDreamDaemonToFirewall(path, cancellationToken)); + /// + /// Creates the BYOND cfg file that prevents the trusted mode dialog from appearing when launching DreamDaemon. + /// + /// The path to the BYOND installation. + /// The for the operation. + /// A representing the running operation. async Task SetNoPromptTrusted(string path, CancellationToken cancellationToken) { var configPath = IOManager.ConcatPath(path, ByondConfigDir); @@ -104,6 +112,12 @@ namespace Tgstation.Server.Host.Components.Byond .ConfigureAwait(false); } + /// + /// Attempt to install the DirectX redistributable included with BYOND. + /// + /// The path to the BYOND installation. + /// The for the operation. + /// A representing the running operation. async Task InstallDirectX(string path, CancellationToken cancellationToken) { using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false); @@ -142,6 +156,12 @@ namespace Tgstation.Server.Host.Components.Byond } } + /// + /// Attempt to add the DreamDaemon executable as an exception to the Windows firewall. + /// + /// The path to the BYOND installation. + /// The for the operation. + /// A representing the running operation. async Task AddDreamDaemonToFirewall(string path, CancellationToken cancellationToken) { var dreamDaemonPath = IOManager.ResolvePath( diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs index e89cd1a2f2..333706e796 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -1,12 +1,12 @@ namespace Tgstation.Server.Host.Components.Chat { /// - /// Represents a mapping of a + /// Represents a mapping of a . /// sealed class ChannelMapping { /// - /// The Id of the + /// The Id of the . /// public long ProviderId { get; set; } @@ -16,12 +16,12 @@ public ulong ProviderChannelId { get; set; } /// - /// If is a watchdog channel + /// If is a watchdog channel. /// public bool IsWatchdogChannel { get; set; } /// - /// If the is an updates channel + /// If the is an updates channel. /// public bool IsUpdatesChannel { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs index 10716ea21e..c5e13f9319 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs @@ -1,7 +1,8 @@ -using Newtonsoft.Json; -using System; +using System; using System.Globalization; +using Newtonsoft.Json; + namespace Tgstation.Server.Host.Components.Chat { /// @@ -11,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Chat public sealed class ChannelRepresentation { /// - /// Backing field for . Represented as a to avoid BYOND percision loss + /// Backing field for . Represented as a to avoid BYOND percision loss. /// public string Id { get; set; } @@ -27,27 +28,27 @@ namespace Tgstation.Server.Host.Components.Chat } /// - /// The user friendly name of the + /// The user friendly name of the . /// public string FriendlyName { get; set; } /// - /// The name of the connection the belongs to + /// The name of the connection the belongs to. /// public string ConnectionName { get; set; } /// - /// If this is considered a channel for admin commands + /// If this is considered a channel for admin commands. /// public bool IsAdminChannel { get; set; } /// - /// If this is a 1-to-1 chat channel + /// If this is a 1-to-1 chat channel. /// public bool IsPrivateChannel { get; set; } /// - /// For user use + /// For user use. /// public string Tag { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index b5f1773526..531a70a2e9 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -1,12 +1,14 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Serilog.Context; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Serilog.Context; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; @@ -18,7 +20,7 @@ namespace Tgstation.Server.Host.Components.Chat { /// // TODO: Decomplexify - #pragma warning disable CA1506 +#pragma warning disable CA1506 sealed class ChatManager : IChatManager, IRestartHandler { /// @@ -27,67 +29,67 @@ namespace Tgstation.Server.Host.Components.Chat public const string CommonMention = "!tgs"; /// - /// The for the + /// The for the . /// readonly IProviderFactory providerFactory; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly ICommandFactory commandFactory; /// - /// The for the + /// The for the . /// readonly IRestartRegistration restartRegistration; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Unchanging s in the mapped by + /// Unchanging s in the mapped by . /// readonly IDictionary builtinCommands; /// - /// Map of s in use, keyed by + /// Map of s in use, keyed by . /// readonly IDictionary providers; /// - /// Map of s to s + /// Map of s to s. /// readonly IDictionary mappedChannels; /// - /// The active s for the + /// The active s for the . /// readonly IList trackingContexts; /// - /// The for + /// The for . /// readonly CancellationTokenSource handlerCts; /// - /// The active for the + /// The active for the . /// readonly List activeChatBots; @@ -97,12 +99,12 @@ namespace Tgstation.Server.Host.Components.Chat readonly object synchronizationLock; /// - /// The for the + /// The for the . /// ICustomCommandHandler customCommandHandler; /// - /// The that monitors incoming chat messages + /// The that monitors incoming chat messages. /// Task chatHandler; @@ -117,12 +119,12 @@ namespace Tgstation.Server.Host.Components.Chat Task messageSendTask; /// - /// The that completes when s change + /// The that completes when s change. /// TaskCompletionSource connectionsUpdated; /// - /// Used for remapping s + /// Used for remapping s. /// ulong channelIdCounter; @@ -132,16 +134,16 @@ namespace Tgstation.Server.Host.Components.Chat long messagesProcessed; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The to populate with - /// The value of - /// The value of - /// The value of - /// The used to populate + /// The value of . + /// The value of . + /// The value of . + /// The to populate with. + /// The value of . + /// The value of . + /// The value of . + /// The used to populate . public ChatManager(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory, ILogger logger, IEnumerable initialChatBots) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); @@ -175,17 +177,337 @@ namespace Tgstation.Server.Host.Components.Chat logger.LogTrace("Disposing..."); restartRegistration.Dispose(); handlerCts.Dispose(); - foreach (var I in providers) - await I.Value.DisposeAsync().ConfigureAwait(false); + foreach (var providerKvp in providers) + await providerKvp.Value.DisposeAsync().ConfigureAwait(false); await messageSendTask.ConfigureAwait(false); } + /// + public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) + { + if (newChannels == null) + throw new ArgumentNullException(nameof(newChannels)); + + logger.LogTrace("ChangeChannels {0}...", connectionId); + var provider = await RemoveProvider(connectionId, false, cancellationToken).ConfigureAwait(false); + if (provider == null) + return; + var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); + lock (activeChatBots) + { + var botToUpdate = activeChatBots.FirstOrDefault(bot => bot.Id == connectionId); + if (botToUpdate != null) + botToUpdate.Channels = newChannels + .Select(apiModel => new Models.ChatChannel + { + DiscordChannelId = apiModel.DiscordChannelId, + IrcChannel = apiModel.IrcChannel, + IsAdminChannel = apiModel.IsAdminChannel, + IsUpdatesChannel = apiModel.IsUpdatesChannel, + IsWatchdogChannel = apiModel.IsWatchdogChannel, + Tag = apiModel.Tag, + }) + .ToList(); + } + + var newMappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping + { + IsWatchdogChannel = x.IsWatchdogChannel == true, + IsUpdatesChannel = x.IsUpdatesChannel == true, + IsAdminChannel = x.IsAdminChannel == true, + ProviderChannelId = y.RealId, + ProviderId = connectionId, + Channel = y, + }); + + ulong baseId; + lock (synchronizationLock) + { + baseId = channelIdCounter; + channelIdCounter += (ulong)results.Count; + } + + Task trackingContextUpdateTask; + lock (mappedChannels) + { + lock (providers) + if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again + return; + foreach (var newMapping in newMappings) + { + var newId = baseId++; + logger.LogTrace("Mapping channel {0}:{1} as {2}", newMapping.Channel.ConnectionName, newMapping.Channel.FriendlyName, newId); + mappedChannels.Add(newId, newMapping); + newMapping.Channel.RealId = newId; + } + + lock (trackingContexts) + trackingContextUpdateTask = Task.WhenAll( + trackingContexts.Select( + x => x.UpdateChannels( + mappedChannels.Select(y => y.Value.Channel).ToList(), + cancellationToken))); + } + + await trackingContextUpdateTask.ConfigureAwait(false); + } + + /// + public async Task ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken) + { + if (newSettings == null) + throw new ArgumentNullException(nameof(newSettings)); + + logger.LogTrace("ChangeSettings..."); + IProvider provider; + + async Task DisconnectProvider(IProvider p) + { + try + { + await p.Disconnect(cancellationToken).ConfigureAwait(false); + } + finally + { + await p.DisposeAsync().ConfigureAwait(false); + } + } + + Task disconnectTask; + lock (providers) + { + // raw settings changes forces a rebuild of the provider + if (providers.TryGetValue(newSettings.Id.Value, out provider)) + { + providers.Remove(newSettings.Id.Value); + disconnectTask = DisconnectProvider(provider); + } + else + disconnectTask = Task.CompletedTask; + if (newSettings.Enabled.Value) + { + provider = providerFactory.CreateProvider(newSettings); + providers.Add(newSettings.Id.Value, provider); + } + } + + lock (mappedChannels) + foreach (var oldMappedChannelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList()) + mappedChannels.Remove(oldMappedChannelId); + + await disconnectTask.ConfigureAwait(false); + + lock (synchronizationLock) + { + // same thread shennanigans + var oldOne = connectionsUpdated; + connectionsUpdated = new TaskCompletionSource(); + oldOne.SetResult(null); + } + + var reconnectionUpdateTask = provider?.SetReconnectInterval( + newSettings.ReconnectionInterval.Value, + newSettings.Enabled.Value) + ?? Task.CompletedTask; + lock (activeChatBots) + { + var originalChatBot = activeChatBots.FirstOrDefault(bot => bot.Id == newSettings.Id); + if (originalChatBot != null) + activeChatBots.Remove(originalChatBot); + + activeChatBots.Add(new Models.ChatBot + { + Id = newSettings.Id, + ConnectionString = newSettings.ConnectionString, + Enabled = newSettings.Enabled, + Name = newSettings.Name, + ReconnectionInterval = newSettings.ReconnectionInterval, + Provider = newSettings.Provider, + }); + } + + await reconnectionUpdateTask.ConfigureAwait(false); + } + + /// + public void QueueMessage(string message, IEnumerable channelIds) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); + if (channelIds == null) + throw new ArgumentNullException(nameof(channelIds)); + + var task = SendMessage(message, channelIds, handlerCts.Token); + AddMessageTask(task); + } + + /// + public async Task QueueWatchdogMessage(string message, CancellationToken cancellationToken) + { + List wdChannels = null; + message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message); + + if (!initialProviderConnectionsTask.IsCompleted) + logger.LogTrace("Waiting for initial provider connections before sending watchdog message..."); + + await initialProviderConnectionsTask.WithToken(cancellationToken).ConfigureAwait(false); + + // so it doesn't change while we're using it + lock (mappedChannels) + wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList(); + + QueueMessage(message, wdChannels); + } + + /// + public Action QueueDeploymentMessage( + Models.RevisionInformation revisionInformation, + Version byondVersion, + DateTimeOffset? estimatedCompletionTime, + string gitHubOwner, + string gitHubRepo, + bool localCommitPushed) + { + List wdChannels; + lock (mappedChannels) // so it doesn't change while we're using it + wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList(); + + logger.LogTrace("Sending deployment message for RevisionInformation: {0}", revisionInformation.Id); + + var callbacks = new List>(); + + var task = Task.WhenAll( + wdChannels.Select( + async x => + { + ChannelMapping channelMapping; + lock (mappedChannels) + if (!mappedChannels.TryGetValue(x, out channelMapping)) + return; + IProvider provider; + lock (providers) + if (!providers.TryGetValue(channelMapping.ProviderId, out provider)) + return; + try + { + var callback = await provider.SendUpdateMessage( + revisionInformation, + byondVersion, + estimatedCompletionTime, + gitHubOwner, + gitHubRepo, + channelMapping.ProviderChannelId, + localCommitPushed, + handlerCts.Token) + .ConfigureAwait(false); + + callbacks.Add(callback); + } + catch (Exception ex) + { + logger.LogWarning( + ex, + "Error sending deploy message to provider {0}!", + channelMapping.ProviderId); + } + })); + + AddMessageTask(task); + + return (errorMessage, dreamMakerOutput) => AddMessageTask( + Task.WhenAll( + callbacks.Select( + x => x( + errorMessage, + dreamMakerOutput)))); + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + foreach (var tgsCommand in commandFactory.GenerateCommands()) + builtinCommands.Add(tgsCommand.Name.ToUpperInvariant(), tgsCommand); + var initialChatBots = activeChatBots.ToList(); + await Task.WhenAll(initialChatBots.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false); + await Task.WhenAll(initialChatBots.Select(x => ChangeChannels(x.Id.Value, x.Channels, cancellationToken))).ConfigureAwait(false); + initialProviderConnectionsTask = InitialConnection(); + chatHandler = MonitorMessages(handlerCts.Token); + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + handlerCts.Cancel(); + if (chatHandler != null) + await chatHandler.ConfigureAwait(false); + await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))).ConfigureAwait(false); + await messageSendTask.ConfigureAwait(false); + } + + /// + public IChatTrackingContext CreateTrackingContext() + { + if (customCommandHandler == null) + throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!"); + + IChatTrackingContext context = null; + lock (mappedChannels) + context = new ChatTrackingContext( + customCommandHandler, + mappedChannels.Select(y => y.Value.Channel), + loggerFactory.CreateLogger(), + () => + { + lock (trackingContexts) + trackingContexts.Remove(context); + }); + + lock (trackingContexts) + trackingContexts.Add(context); + + return context; + } + + /// + public void RegisterCommandHandler(ICustomCommandHandler customCommandHandler) + { + if (this.customCommandHandler != null) + throw new InvalidOperationException("RegisterCommandHandler() already called!"); + this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler)); + } + + /// + public async Task DeleteConnection(long connectionId, CancellationToken cancellationToken) + { + var provider = await RemoveProvider(connectionId, true, cancellationToken).ConfigureAwait(false); + if (provider != null) + try + { + await provider.Disconnect(cancellationToken).ConfigureAwait(false); + } + finally + { + await provider.DisposeAsync().ConfigureAwait(false); + } + } + + /// + public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion); + List wdChannels; + lock (mappedChannels) // so it doesn't change while we're using it + wdChannels = mappedChannels.Select(x => x.Key).ToList(); + + return SendMessage(message, wdChannels, cancellationToken); + } + /// - /// Remove a from and optionally updating the as well + /// Remove a from and optionally updating the as well. /// - /// The of the to delete - /// If should be update + /// The of the to delete. + /// If should be update. /// The for the operation. /// A resulting in the being removed if it exists, otherwise. async Task RemoveProvider(long connectionId, bool updateTrackings, CancellationToken cancellationToken) @@ -202,8 +524,8 @@ namespace Tgstation.Server.Host.Components.Chat Task trackingContextsUpdateTask; lock (mappedChannels) { - foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList()) - mappedChannels.Remove(I); + foreach (var mappedConnectionChannel in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList()) + mappedChannels.Remove(mappedConnectionChannel); var newMappedChannels = mappedChannels.Select(y => y.Value.Channel).ToList(); @@ -219,6 +541,12 @@ namespace Tgstation.Server.Host.Components.Chat return provider; } + /// + /// Remap the channels for a given . + /// + /// The to remap channels for. + /// The for the operation. + /// A representing the running operation. async Task RemapProvider(IProvider provider, CancellationToken cancellationToken) { logger.LogTrace("Remapping channels for provider reconnection..."); @@ -235,12 +563,12 @@ namespace Tgstation.Server.Host.Components.Chat } /// - /// Processes a + /// Processes a . /// - /// The who recevied + /// The who recevied . /// The to process. If , this indicates the provider reconnected. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. #pragma warning disable CA1502 async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) #pragma warning restore CA1502 @@ -280,7 +608,7 @@ namespace Tgstation.Server.Host.Components.Chat IsWatchdogChannel = false, ProviderChannelId = message.User.Channel.RealId, ProviderId = providerId, - Channel = message.User.Channel + Channel = message.User.Channel, }); message.User.Channel.RealId = newId; } @@ -413,10 +741,10 @@ namespace Tgstation.Server.Host.Components.Chat } /// - /// Monitors active providers for new s + /// Monitors active providers for new s. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. async Task MonitorMessages(CancellationToken cancellationToken) { logger.LogTrace("Starting processing loop..."); @@ -426,11 +754,11 @@ namespace Tgstation.Server.Host.Components.Chat while (!cancellationToken.IsCancellationRequested) { // prune disconnected providers - foreach (var I in messageTasks.Where(x => !x.Key.Disposed).ToList()) + foreach (var undisposedMessageTaskKvp in messageTasks.Where(x => !x.Key.Disposed).ToList()) { - messageTasks.Remove(I.Key); - if (I.Value.IsCompleted) - (await I.Value.ConfigureAwait(false))?.Context?.Dispose(); + messageTasks.Remove(undisposedMessageTaskKvp.Key); + if (undisposedMessageTaskKvp.Value.IsCompleted) + (await undisposedMessageTaskKvp.Value.ConfigureAwait(false))?.Context?.Dispose(); } // add new ones @@ -438,9 +766,9 @@ namespace Tgstation.Server.Host.Components.Chat lock (synchronizationLock) updatedTask = connectionsUpdated.Task; lock (providers) - foreach (var I in providers) - if (!messageTasks.ContainsKey(I.Value)) - messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken)); + foreach (var providerKvp in providers) + if (!messageTasks.ContainsKey(providerKvp.Value)) + messageTasks.Add(providerKvp.Value, providerKvp.Value.NextMessage(cancellationToken)); if (messageTasks.Count == 0) { @@ -452,14 +780,14 @@ namespace Tgstation.Server.Host.Components.Chat await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false); // process completed ones - foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) + foreach (var completedMessageTaskKvp in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { - var message = await I.Value.ConfigureAwait(false); + var message = await completedMessageTaskKvp.Value.ConfigureAwait(false); using var messageContext = message?.Context; var messageNumber = Interlocked.Increment(ref messagesProcessed); using (LogContext.PushProperty("ChatMessage", messageNumber)) - await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); - messageTasks.Remove(I.Key); + await ProcessMessage(completedMessageTaskKvp.Key, message, cancellationToken).ConfigureAwait(false); + messageTasks.Remove(completedMessageTaskKvp.Key); } } } @@ -475,165 +803,6 @@ namespace Tgstation.Server.Host.Components.Chat logger.LogTrace("Leaving message processing loop"); } - /// - public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) - { - if (newChannels == null) - throw new ArgumentNullException(nameof(newChannels)); - - logger.LogTrace("ChangeChannels {0}...", connectionId); - var provider = await RemoveProvider(connectionId, false, cancellationToken).ConfigureAwait(false); - if (provider == null) - return; - var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); - lock (activeChatBots) - { - var botToUpdate = activeChatBots.FirstOrDefault(bot => bot.Id == connectionId); - if (botToUpdate != null) - botToUpdate.Channels = newChannels - .Select(apiModel => new Models.ChatChannel - { - DiscordChannelId = apiModel.DiscordChannelId, - IrcChannel = apiModel.IrcChannel, - IsAdminChannel = apiModel.IsAdminChannel, - IsUpdatesChannel = apiModel.IsUpdatesChannel, - IsWatchdogChannel = apiModel.IsWatchdogChannel, - Tag = apiModel.Tag - }) - .ToList(); - } - - var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping - { - IsWatchdogChannel = x.IsWatchdogChannel == true, - IsUpdatesChannel = x.IsUpdatesChannel == true, - IsAdminChannel = x.IsAdminChannel == true, - ProviderChannelId = y.RealId, - ProviderId = connectionId, - Channel = y - }); - - ulong baseId; - lock (synchronizationLock) - { - baseId = channelIdCounter; - channelIdCounter += (ulong)results.Count; - } - - Task trackingContextUpdateTask; - lock (mappedChannels) - { - lock (providers) - if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again - return; - foreach (var I in mappings) - { - var newId = baseId++; - logger.LogTrace("Mapping channel {0}:{1} as {2}", I.Channel.ConnectionName, I.Channel.FriendlyName, newId); - mappedChannels.Add(newId, I); - I.Channel.RealId = newId; - } - - lock (trackingContexts) - trackingContextUpdateTask = Task.WhenAll( - trackingContexts.Select( - x => x.UpdateChannels( - mappedChannels.Select(y => y.Value.Channel).ToList(), - cancellationToken))); - } - - await trackingContextUpdateTask.ConfigureAwait(false); - } - - /// - public async Task ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken) - { - if (newSettings == null) - throw new ArgumentNullException(nameof(newSettings)); - - logger.LogTrace("ChangeSettings..."); - IProvider provider; - - async Task DisconnectProvider(IProvider p) - { - try - { - await p.Disconnect(cancellationToken).ConfigureAwait(false); - } - finally - { - await p.DisposeAsync().ConfigureAwait(false); - } - } - - Task disconnectTask; - lock (providers) - { - // raw settings changes forces a rebuild of the provider - if (providers.TryGetValue(newSettings.Id.Value, out provider)) - { - providers.Remove(newSettings.Id.Value); - disconnectTask = DisconnectProvider(provider); - } - else - disconnectTask = Task.CompletedTask; - if (newSettings.Enabled.Value) - { - provider = providerFactory.CreateProvider(newSettings); - providers.Add(newSettings.Id.Value, provider); - } - } - - lock (mappedChannels) - foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList()) - mappedChannels.Remove(I); - - await disconnectTask.ConfigureAwait(false); - - lock (synchronizationLock) - { - // same thread shennanigans - var oldOne = connectionsUpdated; - connectionsUpdated = new TaskCompletionSource(); - oldOne.SetResult(null); - } - - var reconnectionUpdateTask = provider?.SetReconnectInterval( - newSettings.ReconnectionInterval.Value, - newSettings.Enabled.Value) - ?? Task.CompletedTask; - lock (activeChatBots) - { - var originalChatBot = activeChatBots.FirstOrDefault(bot => bot.Id == newSettings.Id); - if (originalChatBot != null) - activeChatBots.Remove(originalChatBot); - - activeChatBots.Add(new Models.ChatBot - { - Id = newSettings.Id, - ConnectionString = newSettings.ConnectionString, - Enabled = newSettings.Enabled, - Name = newSettings.Name, - ReconnectionInterval = newSettings.ReconnectionInterval, - Provider = newSettings.Provider - }); - } - - await reconnectionUpdateTask.ConfigureAwait(false); - } - - /// - public void QueueMessage(string message, IEnumerable channelIds) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); - if (channelIds == null) - throw new ArgumentNullException(nameof(channelIds)); - - var task = SendMessage(message, channelIds, handlerCts.Token); - AddMessageTask(task); - } - /// /// Asynchronously send a given to a set of . /// @@ -660,99 +829,6 @@ namespace Tgstation.Server.Host.Components.Chat })); } - /// - public async Task QueueWatchdogMessage(string message, CancellationToken cancellationToken) - { - List wdChannels = null; - message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message); - - if (!initialProviderConnectionsTask.IsCompleted) - logger.LogTrace("Waiting for initial provider connections before sending watchdog message..."); - - await initialProviderConnectionsTask.WithToken(cancellationToken).ConfigureAwait(false); - - // so it doesn't change while we're using it - lock (mappedChannels) - wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList(); - - QueueMessage(message, wdChannels); - } - - /// - public Action QueueDeploymentMessage( - Models.RevisionInformation revisionInformation, - Version byondVersion, - DateTimeOffset? estimatedCompletionTime, - string gitHubOwner, - string gitHubRepo, - bool localCommitPushed) - { - List wdChannels; - lock (mappedChannels) // so it doesn't change while we're using it - wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList(); - - logger.LogTrace("Sending deployment message for RevisionInformation: {0}", revisionInformation.Id); - - var callbacks = new List>(); - - var task = Task.WhenAll( - wdChannels.Select( - async x => - { - ChannelMapping channelMapping; - lock (mappedChannels) - if (!mappedChannels.TryGetValue(x, out channelMapping)) - return; - IProvider provider; - lock (providers) - if (!providers.TryGetValue(channelMapping.ProviderId, out provider)) - return; - try - { - var callback = await provider.SendUpdateMessage( - revisionInformation, - byondVersion, - estimatedCompletionTime, - gitHubOwner, - gitHubRepo, - channelMapping.ProviderChannelId, - localCommitPushed, - handlerCts.Token) - .ConfigureAwait(false); - - callbacks.Add(callback); - } - catch (Exception ex) - { - logger.LogWarning( - ex, - "Error sending deploy message to provider {0}!", - channelMapping.ProviderId); - } - })); - - AddMessageTask(task); - - return (errorMessage, dreamMakerOutput) => AddMessageTask( - Task.WhenAll( - callbacks.Select( - x => x( - errorMessage, - dreamMakerOutput)))); - } - - /// - public async Task StartAsync(CancellationToken cancellationToken) - { - foreach (var I in commandFactory.GenerateCommands()) - builtinCommands.Add(I.Name.ToUpperInvariant(), I); - var initialChatBots = activeChatBots.ToList(); - await Task.WhenAll(initialChatBots.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false); - await Task.WhenAll(initialChatBots.Select(x => ChangeChannels(x.Id.Value, x.Channels, cancellationToken))).ConfigureAwait(false); - initialProviderConnectionsTask = InitialConnection(); - chatHandler = MonitorMessages(handlerCts.Token); - } - /// /// Aggregate all s into one . /// @@ -763,74 +839,6 @@ namespace Tgstation.Server.Host.Components.Chat logger.LogTrace("Initial provider connection task completed"); } - /// - public async Task StopAsync(CancellationToken cancellationToken) - { - handlerCts.Cancel(); - if (chatHandler != null) - await chatHandler.ConfigureAwait(false); - await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))).ConfigureAwait(false); - await messageSendTask.ConfigureAwait(false); - } - - /// - public IChatTrackingContext CreateTrackingContext() - { - if (customCommandHandler == null) - throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!"); - - IChatTrackingContext context = null; - lock (mappedChannels) - context = new ChatTrackingContext( - customCommandHandler, - mappedChannels.Select(y => y.Value.Channel), - loggerFactory.CreateLogger(), - () => - { - lock (trackingContexts) - trackingContexts.Remove(context); - }); - - lock (trackingContexts) - trackingContexts.Add(context); - - return context; - } - - /// - public void RegisterCommandHandler(ICustomCommandHandler customCommandHandler) - { - if (this.customCommandHandler != null) - throw new InvalidOperationException("RegisterCommandHandler() already called!"); - this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler)); - } - - /// - public async Task DeleteConnection(long connectionId, CancellationToken cancellationToken) - { - var provider = await RemoveProvider(connectionId, true, cancellationToken).ConfigureAwait(false); - if (provider != null) - try - { - await provider.Disconnect(cancellationToken).ConfigureAwait(false); - } - finally - { - await provider.DisposeAsync().ConfigureAwait(false); - } - } - - /// - public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) - { - var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion); - List wdChannels; - lock (mappedChannels) // so it doesn't change while we're using it - wdChannels = mappedChannels.Select(x => x.Key).ToList(); - - return SendMessage(message, wdChannels, cancellationToken); - } - /// /// Adds a given to . /// diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs index 0ca59ef63c..42ff35ecbe 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; using Tgstation.Server.Host.Core; @@ -13,32 +15,32 @@ namespace Tgstation.Server.Host.Components.Chat sealed class ChatManagerFactory : IChatManagerFactory { /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the . /// readonly IProviderFactory providerFactory; /// - /// The for the + /// The for the . /// readonly IServerControl serverControl; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . public ChatManagerFactory( IProviderFactory providerFactory, IServerControl serverControl, diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs index 118a11df26..8239bdb0e1 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat @@ -80,7 +82,7 @@ namespace Tgstation.Server.Host.Components.Chat bool active; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The initial value of . @@ -142,4 +144,4 @@ namespace Tgstation.Server.Host.Components.Chat return updateTask; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs b/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs index f5a7c0fb26..6de07109ea 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs @@ -1,21 +1,22 @@ -using Newtonsoft.Json; -using System; +using System; using System.Globalization; +using Newtonsoft.Json; + namespace Tgstation.Server.Host.Components.Chat { /// - /// Represents a tgs_chat_user datum + /// Represents a tgs_chat_user datum. /// public sealed class ChatUser { /// - /// Backing field for . Represented as a to avoid BYOND percision loss + /// Backing field for . Represented as a to avoid BYOND percision loss. /// public string Id { get; set; } /// - /// The internal user id + /// The internal user id. /// [JsonIgnore] public ulong RealId @@ -25,17 +26,17 @@ namespace Tgstation.Server.Host.Components.Chat } /// - /// The friendly name of the user + /// The friendly name of the user. /// public string FriendlyName { get; set; } /// - /// The text to mention the user + /// The text to mention the user. /// public string Mention { get; set; } /// - /// The the user spoke from + /// The the user spoke from. /// public ChannelRepresentation Channel { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs index 2d8eeaabd8..f338c2a602 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Watchdog; @@ -10,7 +11,7 @@ using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// For displaying the installed Byond version + /// For displaying the installed Byond version. /// sealed class ByondCommand : ICommand { @@ -24,20 +25,20 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly => false; /// - /// The for the + /// The for the . /// readonly IByondManager byondManager; /// - /// The for the + /// The for the . /// readonly IWatchdog watchdog; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public ByondCommand(IByondManager byondManager, IWatchdog watchdog) { this.byondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager)); diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs index 21477df61f..6159a2cc20 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; + using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; @@ -12,43 +13,43 @@ namespace Tgstation.Server.Host.Components.Chat.Commands sealed class CommandFactory : ICommandFactory { /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IByondManager byondManager; /// - /// The for the + /// The for the . /// readonly IRepositoryManager repositoryManager; /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly Models.Instance instance; /// - /// The for the + /// The for the . /// IWatchdog watchdog; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . public CommandFactory( IAssemblyInformationProvider assemblyInformationProvider, IByondManager byondManager, @@ -64,9 +65,9 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// - /// Set a for the + /// Set a for the . /// - /// The to set + /// The to set. public void SetWatchdog(IWatchdog watchdog) { if (this.watchdog != null) @@ -85,8 +86,8 @@ namespace Tgstation.Server.Host.Components.Chat.Commands new ByondCommand(byondManager, watchdog), new RevisionCommand(watchdog, repositoryManager), new PullRequestsCommand(watchdog, repositoryManager, databaseContextFactory, instance), - new KekCommand() + new KekCommand(), }; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index 6c4a40b9b4..3628e734cf 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// Represents a command made from DM code + /// Represents a command made from DM code. /// public sealed class CustomCommand : ICommand { @@ -19,14 +19,14 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly { get; set; } /// - /// The for the + /// The for the . /// ICustomCommandHandler handler; /// - /// Set a new + /// Set a new . /// - /// The value of + /// The value of . public void SetHandler(ICustomCommandHandler handler) { if (this.handler != null) diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs index 7d29b2507b..1ea6803a3d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs @@ -4,32 +4,32 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// Represents a command that can be invoked by talking to chat bots + /// Represents a command that can be invoked by talking to chat bots. /// public interface ICommand { /// - /// The text to invoke the command. May not be "?" or "help" (case-insensitive) + /// The text to invoke the command. May not be "?" or "help" (case-insensitive). /// string Name { get; } /// - /// The help text to display when queires are made about the command + /// The help text to display when queires are made about the command. /// string HelpText { get; } /// - /// If the command should only be available to s who's has set + /// If the command should only be available to s who's has set. /// bool AdminOnly { get; } /// - /// Invoke the + /// Invoke the . /// - /// The text after with leading whitespace trimmed - /// The who invoked the command - /// The for the operation - /// A resulting in a to send to the invoker + /// The text after with leading whitespace trimmed. + /// The who invoked the command. + /// The for the operation. + /// A resulting in a to send to the invoker. Task Invoke(string arguments, ChatUser user, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs index 5520126604..c3ffa50d74 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs @@ -3,14 +3,14 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// Factory for built in s + /// Factory for built in s. /// interface ICommandFactory { /// - /// Generate builtin s + /// Generate builtin s. /// - /// A of s + /// A of s. IReadOnlyList GenerateCommands(); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs index 2916bf676a..5a3fa07f28 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs @@ -4,12 +4,12 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// kek + /// kek. /// sealed class KekCommand : ICommand { /// - /// kek + /// kek. /// const string Kek = "kek"; diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs index 617a67f6ac..7f7b2ea3dc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs @@ -1,10 +1,12 @@ -using Microsoft.EntityFrameworkCore; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; @@ -13,7 +15,7 @@ using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// Command for reading the active s + /// Command for reading the active s. /// sealed class PullRequestsCommand : ICommand { @@ -27,32 +29,32 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly => false; /// - /// The for the + /// The for the . /// readonly IWatchdog watchdog; /// - /// The for the + /// The for the . /// readonly IRepositoryManager repositoryManager; /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly Models.Instance instance; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . public PullRequestsCommand(IWatchdog watchdog, IRepositoryManager repositoryManager, IDatabaseContextFactory databaseContextFactory, Models.Instance instance) { this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); @@ -63,7 +65,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// // TODO: Decomplexify - #pragma warning disable CA1506 +#pragma warning disable CA1506 public async Task Invoke(string arguments, ChatUser user, CancellationToken cancellationToken) { IEnumerable results = null; @@ -87,7 +89,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands .Select(x => new Models.TestMerge { Number = x.Number, - TargetCommitSha = x.TargetCommitSha + TargetCommitSha = x.TargetCommitSha, }) .ToListAsync(cancellationToken) .ConfigureAwait(false)) @@ -102,6 +104,6 @@ namespace Tgstation.Server.Host.Components.Chat.Commands return !results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha.Substring(0, 7)))); } - #pragma warning restore CA1506 +#pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs index 014322a25f..05f1b5e06a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; @@ -10,7 +11,7 @@ using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// For displaying + /// For displaying . /// sealed class RevisionCommand : ICommand { @@ -24,20 +25,20 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly => false; /// - /// The for the + /// The for the . /// readonly IWatchdog watchdog; /// - /// The for the + /// The for the . /// readonly IRepositoryManager repositoryManager; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public RevisionCommand(IWatchdog watchdog, IRepositoryManager repositoryManager) { this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs index 3b5a92e1c4..94c7185e49 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -1,12 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Chat.Commands { /// - /// to return the + /// to return the . /// sealed class VersionCommand : ICommand { @@ -20,14 +21,14 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly => false; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public VersionCommand(IAssemblyInformationProvider assemblyInformationProvider) { this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider)); diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs index 5bd0117ab1..2cad279068 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs @@ -1,46 +1,48 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Chat { /// - /// For managing connected chat services + /// For managing connected chat services. /// public interface IChatManager : IHostedService, IAsyncDisposable { /// - /// Registers a to use + /// Registers a to use. /// - /// A + /// A . void RegisterCommandHandler(ICustomCommandHandler customCommandHandler); /// - /// Change chat settings. If the is not currently in use, a new connection will be made instead + /// Change chat settings. If the is not currently in use, a new connection will be made instead. /// - /// The new - /// The for the operation - /// A representing the running operation. Will complete immediately if the property of is + /// The new . + /// The for the operation. + /// A representing the running operation. Will complete immediately if the property of is . Task ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken); /// - /// Disconnects and deletes a given connection + /// Disconnects and deletes a given connection. /// - /// The of the connection - /// The for the operation - /// A representing the running operation + /// The of the connection. + /// The for the operation. + /// A representing the running operation. Task DeleteConnection(long connectionId, CancellationToken cancellationToken); /// - /// Change chat channels + /// Change chat channels. /// - /// The of the connection - /// An of the new list of s - /// The for the operation - /// A representing the running operation + /// The of the connection. + /// An of the new list of s. + /// The for the operation. + /// A representing the running operation. Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManagerFactory.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManagerFactory.cs index c7aa197d2e..05313f3187 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatManagerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatManagerFactory.cs @@ -1,21 +1,22 @@ using System.Collections.Generic; + using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { /// - /// For creating s + /// For creating s. /// interface IChatManagerFactory { /// - /// Create a + /// Create a . /// - /// The for the - /// The for the - /// The initial for the - /// A new + /// The for the . + /// The for the . + /// The initial for the . + /// A new . IChatManager CreateChatManager(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable initialChatBots); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IChatTrackingContext.cs index e4e4bc34ed..ae291c2424 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatTrackingContext.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; + using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat { /// - /// Represents a tracking of dynamic chat json files + /// Represents a tracking of dynamic chat json files. /// public interface IChatTrackingContext : IChannelSink, IDisposable { @@ -30,4 +31,4 @@ namespace Tgstation.Server.Host.Components.Chat /// The s to send updates to. void SetChannelSink(IChannelSink channelSink); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs index 33f22063a7..3c19cee2ba 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs @@ -4,18 +4,18 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat { /// - /// Handles s that map to those defined in a + /// Handles s that map to those defined in a . /// public interface ICustomCommandHandler { /// - /// Handle a chat command + /// Handle a chat command. /// - /// The command name - /// Everything typed after minus leading spaces - /// The sending - /// The for the operation - /// A resulting in the response text to send back + /// The command name. + /// Everything typed after minus leading spaces. + /// The sending . + /// The for the operation. + /// A resulting in the response text to send back. Task HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index ef87a84d34..8f04d8b2ea 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -1,19 +1,19 @@ -using System; +using System; namespace Tgstation.Server.Host.Components.Chat.Providers { /// - /// Represents a message recieved by a + /// Represents a message recieved by a . /// sealed class Message { /// - /// The text of the message + /// The text of the message. /// public string Content { get; set; } /// - /// The who sent the + /// The who sent the . /// public ChatUser User { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 1817808d27..6a5110656d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -1,11 +1,13 @@ -using Discord; -using Discord.WebSocket; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Discord; +using Discord.WebSocket; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; @@ -14,7 +16,7 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Chat.Providers { /// - /// for the Discord app + /// for the Discord app. /// sealed class DiscordProvider : Provider { @@ -38,12 +40,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly DiscordSocketClient client; /// - /// of mapped s + /// of mapped s. /// readonly List mappedChannels; @@ -63,14 +65,68 @@ namespace Tgstation.Server.Host.Components.Chat.Providers readonly DiscordDMOutputDisplayType outputDisplayType; /// - /// Normalize a discord mention string + /// Normalize a discord mention string. /// - /// The mention provided by the Discord library - /// The normalized mention + /// The mention provided by the Discord library. + /// The normalized mention . static string NormalizeMentions(string fromDiscord) => fromDiscord.Replace("<@!", "<@", StringComparison.Ordinal); /// - /// Construct a + /// Create a of s for a discord update embed. + /// + /// The of the deployment. + /// The BYOND of the deployment. + /// The repository GitHub owner, if any. + /// The repository GitHub name, if any. + /// if the local deployment commit was pushed to the remote repository. + /// A new of s to use. + static List BuildUpdateEmbedFields( + Models.RevisionInformation revisionInformation, + Version byondVersion, + string gitHubOwner, + string gitHubRepo, + bool localCommitPushed) + { + bool gitHub = gitHubOwner != null && gitHubRepo != null; + var fields = new List + { + new EmbedFieldBuilder + { + Name = "BYOND Version", + Value = $"{byondVersion.Major}.{byondVersion.Minor}{(byondVersion.Build > 0 ? $".{byondVersion.Build}" : String.Empty)}", + IsInline = true, + }, + new EmbedFieldBuilder + { + Name = "Local Commit", + Value = localCommitPushed && gitHub + ? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})" + : revisionInformation.CommitSha.Substring(0, 7), + IsInline = true, + }, + new EmbedFieldBuilder + { + Name = "Branch Commit", + Value = gitHub + ? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})" + : revisionInformation.OriginCommitSha.Substring(0, 7), + IsInline = true, + }, + }; + + fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty()) + .Select(x => x.TestMerge) + .Select(x => new EmbedFieldBuilder + { + Name = $"#{x.Number}", + Value = $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}", + })); + + return fields; + } + + /// + /// Initializes a new instance of the class. /// /// The for the . /// The value of . @@ -102,86 +158,244 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.Dispose(); } - /// - /// Handle a message recieved from Discord - /// - /// The - /// A representing the running operation - async Task Client_MessageReceived(SocketMessage e) + /// + public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) { - if (e.Author.Id == client.CurrentUser.Id) - return; + if (channels == null) + throw new ArgumentNullException(nameof(channels)); - IDisposable typingState = null; - void StartTyping() => typingState = e.Channel.EnterTypingState(); - try + if (!Connected) { - if (basedMeme && e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase)) - { - StartTyping(); + Logger.LogWarning("Cannot map channels, provider disconnected!"); + return Task.FromResult>(Array.Empty()); + } - // DCT: None available - await SendMessage( - e.Channel.Id, - "https://youtu.be/LrNu-SuFF_o", - default) - .ConfigureAwait(false); - return; + ChannelRepresentation GetModelChannelFromDBChannel(Api.Models.ChatChannel channelFromDB) + { + if (!channelFromDB.DiscordChannelId.HasValue) + throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); + + var channelId = channelFromDB.DiscordChannelId.Value; + ulong discordChannelId; + string connectionName; + string friendlyName; + if (channelId == 0) + { + connectionName = client.CurrentUser.Username; + friendlyName = "(Unmapped accessible channels)"; + discordChannelId = 0; + } + else + { + var discordChannel = client.GetChannel(channelId); + if (!(discordChannel is ITextChannel textChannel)) + { + Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType()); + return null; + } + + discordChannelId = textChannel.Id; + connectionName = textChannel.Guild.Name; + friendlyName = textChannel.Name; } - var pm = e.Channel is IPrivateChannel; - var shouldNotAnswer = !pm; - if (shouldNotAnswer) - lock (mappedChannels) - shouldNotAnswer = !mappedChannels.Contains(e.Channel.Id); - - var content = NormalizeMentions(e.Content); - var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id) - || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase)); - if (mentionedUs) - StartTyping(); - - if (shouldNotAnswer) + var channelModel = new ChannelRepresentation { - if (mentionedUs) - { - Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username); + RealId = discordChannelId, + IsAdminChannel = channelFromDB.IsAdminChannel == true, + ConnectionName = connectionName, + FriendlyName = friendlyName, + IsPrivateChannel = false, + Tag = channelFromDB.Tag, + }; + Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName); + return channelModel; + } - // DCT: None available - await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false); + var enumerator = channels + .Select(x => GetModelChannelFromDBChannel(x)) + .Where(x => x != null).ToList(); + + lock (mappedChannels) + { + mappedChannels.Clear(); + mappedChannels.AddRange(enumerator.Select(x => x.RealId)); + } + + return Task.FromResult>(enumerator); + } + + /// + public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) + { + var requestOptions = new RequestOptions + { + CancelToken = cancellationToken, + Timeout = 10000, // prevent stupid long hold ups from this + }; + + Task SendToChannel(IMessageChannel channel) => channel.SendMessageAsync( + message, + false, + null, + requestOptions); + + try + { + if (channelId == 0) + { + var unmappedTextChannels = client + .Guilds + .SelectMany(x => x.TextChannels); + + lock (mappedChannels) + unmappedTextChannels = unmappedTextChannels.Where(x => !mappedChannels.Contains(x.Id)); + + // discord API confirmed weak boned: https://stackoverflow.com/a/52462336 + var channelCount = 0UL; + var tasks = unmappedTextChannels + .Select(x => + { + ++channelCount; + return SendToChannel(x); + }); + + if (channelCount > 0) + { + Logger.LogTrace("Dispatched to {0} unmapped channels...", channelCount); + await Task.WhenAll(tasks).ConfigureAwait(false); } return; } - var result = new Message - { - Content = content, - User = new ChatUser - { - RealId = e.Author.Id, - Channel = new ChannelRepresentation - { - RealId = e.Channel.Id, - IsPrivateChannel = pm, - ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", - FriendlyName = e.Channel.Name + if (!(client.GetChannel(channelId) is IMessageChannel channel)) + return; - // isAdmin and Tag populated by manager - }, - FriendlyName = e.Author.Username, - Mention = NormalizeMentions(e.Author.Mention) - }, - Context = typingState + await SendToChannel(channel).ConfigureAwait(false); + } + catch (Exception e) + { + if (e is OperationCanceledException) + cancellationToken.ThrowIfCancellationRequested(); + Logger.LogWarning(e, "Error sending discord message!"); + } + } + + /// + public override async Task> SendUpdateMessage( + Models.RevisionInformation revisionInformation, + Version byondVersion, + DateTimeOffset? estimatedCompletionTime, + string gitHubOwner, + string gitHubRepo, + ulong channelId, + bool localCommitPushed, + CancellationToken cancellationToken) + { + localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha; + + var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed); + var builder = new EmbedBuilder + { + Author = new EmbedAuthorBuilder + { + Name = assemblyInformationProvider.VersionPrefix, + Url = "https://github.com/tgstation/tgstation-server", + IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4", + }, + Color = Color.Gold, + Description = "TGS has begun deploying active repository code to production.", + Fields = fields, + Title = "Code Deployment", + Footer = new EmbedFooterBuilder + { + Text = $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}", + }, + Timestamp = estimatedCompletionTime, + }; + + Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId); + if (!(client.GetChannel(channelId) is IMessageChannel channel)) + { + Logger.LogTrace("Channel ID {0} does not exist or is not an IMessageChannel!", channelId); + return (errorMessage, dreamMakerOutput) => Task.CompletedTask; + } + + var message = await channel.SendMessageAsync( + "DM: Deployment in Progress...", + false, + builder.Build(), + new RequestOptions + { + CancelToken = cancellationToken, + }) + .ConfigureAwait(false); + + return async (errorMessage, dreamMakerOutput) => + { + var completionString = errorMessage == null ? "Succeeded" : "Failed"; + builder.Footer.Text = completionString; + builder.Color = errorMessage == null ? Color.Green : Color.Red; + builder.Timestamp = DateTimeOffset.UtcNow; + builder.Description = errorMessage == null + ? "The deployment completed successfully and will be available at the next server reboot." + : "The deployment failed."; + + var showDMOutput = outputDisplayType switch + { + DiscordDMOutputDisplayType.Always => true, + DiscordDMOutputDisplayType.Never => false, + DiscordDMOutputDisplayType.OnError => errorMessage != null, + _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"), }; - EnqueueMessage(result); - typingState = null; - } - finally - { - typingState?.Dispose(); - } + if (dreamMakerOutput != null) + { + showDMOutput = showDMOutput && dreamMakerOutput.Length < EmbedFieldBuilder.MaxFieldValueLength - (6 + Environment.NewLine.Length); + if (showDMOutput) + builder.AddField(new EmbedFieldBuilder + { + Name = "DreamMaker Output", + Value = $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```", + }); + } + + if (errorMessage != null) + builder.AddField(new EmbedFieldBuilder + { + Name = "Error Message", + Value = errorMessage, + }); + + var updatedMessage = $"DM: Deployment {completionString}!"; + try + { + await message.ModifyAsync( + props => + { + props.Content = updatedMessage; + props.Embed = builder.Build(); + }) + .ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Updating deploy embed {0} failed, attempting new post!", message.Id); + try + { + await channel.SendMessageAsync( + updatedMessage, + false, + builder.Build()) + .ConfigureAwait(false); + } + catch (Exception ex2) + { + Logger.LogWarning(ex2, "Posting completion deploy embed failed!"); + } + } + }; } /// @@ -247,298 +461,86 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } } - /// - public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + /// + /// Handle a message recieved from Discord. + /// + /// The . + /// A representing the running operation. + async Task Client_MessageReceived(SocketMessage e) { - if (channels == null) - throw new ArgumentNullException(nameof(channels)); - - if (!Connected) - { - Logger.LogWarning("Cannot map channels, provider disconnected!"); - return Task.FromResult>(Array.Empty()); - } - - ChannelRepresentation GetModelChannelFromDBChannel(Api.Models.ChatChannel channelFromDB) - { - if (!channelFromDB.DiscordChannelId.HasValue) - throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); - - var channelId = channelFromDB.DiscordChannelId.Value; - ulong discordChannelId; - string connectionName; - string friendlyName; - if (channelId == 0) - { - connectionName = client.CurrentUser.Username; - friendlyName = "(Unmapped accessible channels)"; - discordChannelId = 0; - } - else - { - var discordChannel = client.GetChannel(channelId); - if (!(discordChannel is ITextChannel textChannel)) - { - Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType()); - return null; - } - - discordChannelId = textChannel.Id; - connectionName = textChannel.Guild.Name; - friendlyName = textChannel.Name; - } - - var channelModel = new ChannelRepresentation - { - RealId = discordChannelId, - IsAdminChannel = channelFromDB.IsAdminChannel == true, - ConnectionName = connectionName, - FriendlyName = friendlyName, - IsPrivateChannel = false, - Tag = channelFromDB.Tag - }; - Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName); - return channelModel; - } - - var enumerator = channels - .Select(x => GetModelChannelFromDBChannel(x)) - .Where(x => x != null).ToList(); - - lock (mappedChannels) - { - mappedChannels.Clear(); - mappedChannels.AddRange(enumerator.Select(x => x.RealId)); - } - - return Task.FromResult>(enumerator); - } - - /// - public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) - { - var requestOptions = new RequestOptions - { - CancelToken = cancellationToken, - Timeout = 10000 // prevent stupid long hold ups from this - }; - - Task SendToChannel(IMessageChannel channel) => channel.SendMessageAsync( - message, - false, - null, - requestOptions); + if (e.Author.Id == client.CurrentUser.Id) + return; + IDisposable typingState = null; + void StartTyping() => typingState = e.Channel.EnterTypingState(); try { - if (channelId == 0) + if (basedMeme && e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase)) { - var unmappedTextChannels = client - .Guilds - .SelectMany(x => x.TextChannels); + StartTyping(); + // DCT: None available + await SendMessage( + e.Channel.Id, + "https://youtu.be/LrNu-SuFF_o", + default) + .ConfigureAwait(false); + return; + } + + var pm = e.Channel is IPrivateChannel; + var shouldNotAnswer = !pm; + if (shouldNotAnswer) lock (mappedChannels) - unmappedTextChannels = unmappedTextChannels.Where(x => !mappedChannels.Contains(x.Id)); + shouldNotAnswer = !mappedChannels.Contains(e.Channel.Id); - // discord API confirmed weak boned: https://stackoverflow.com/a/52462336 - var channelCount = 0UL; - var tasks = unmappedTextChannels - .Select(x => - { - ++channelCount; - return SendToChannel(x); - }); + var content = NormalizeMentions(e.Content); + var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id) + || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase)); + if (mentionedUs) + StartTyping(); - if (channelCount > 0) + if (shouldNotAnswer) + { + if (mentionedUs) { - Logger.LogTrace("Dispatched to {0} unmapped channels...", channelCount); - await Task.WhenAll(tasks).ConfigureAwait(false); + Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username); + + // DCT: None available + await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false); } return; } - if (!(client.GetChannel(channelId) is IMessageChannel channel)) - return; - - await SendToChannel(channel).ConfigureAwait(false); - } - catch (Exception e) - { - if (e is OperationCanceledException) - cancellationToken.ThrowIfCancellationRequested(); - Logger.LogWarning(e, "Error sending discord message!"); - } - } - - /// - /// Create a of s for a discord update embed. - /// - /// The of the deployment. - /// The BYOND of the deployment. - /// The repository GitHub owner, if any. - /// The repository GitHub name, if any. - /// if the local deployment commit was pushed to the remote repository. - /// A new of s to use. - static List BuildUpdateEmbedFields( - Models.RevisionInformation revisionInformation, - Version byondVersion, - string gitHubOwner, - string gitHubRepo, - bool localCommitPushed) - { - bool gitHub = gitHubOwner != null && gitHubRepo != null; - var fields = new List - { - new EmbedFieldBuilder + var result = new Message { - Name = "BYOND Version", - Value = $"{byondVersion.Major}.{byondVersion.Minor}{(byondVersion.Build > 0 ? $".{byondVersion.Build}" : String.Empty)}", - IsInline = true - }, - new EmbedFieldBuilder - { - Name = "Local Commit", - Value = localCommitPushed && gitHub - ? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})" - : revisionInformation.CommitSha.Substring(0, 7), - IsInline = true - }, - new EmbedFieldBuilder - { - Name = "Branch Commit", - Value = gitHub - ? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})" - : revisionInformation.OriginCommitSha.Substring(0, 7), - IsInline = true - } - }; + Content = content, + User = new ChatUser + { + RealId = e.Author.Id, + Channel = new ChannelRepresentation + { + RealId = e.Channel.Id, + IsPrivateChannel = pm, + ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", + FriendlyName = e.Channel.Name, - fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty()) - .Select(x => x.TestMerge) - .Select(x => new EmbedFieldBuilder - { - Name = $"#{x.Number}", - Value = $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}" - })); - - return fields; - } - - /// - public override async Task> SendUpdateMessage( - Models.RevisionInformation revisionInformation, - Version byondVersion, - DateTimeOffset? estimatedCompletionTime, - string gitHubOwner, - string gitHubRepo, - ulong channelId, - bool localCommitPushed, - CancellationToken cancellationToken) - { - localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha; - - var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed); - var builder = new EmbedBuilder - { - Author = new EmbedAuthorBuilder - { - Name = assemblyInformationProvider.VersionPrefix, - Url = "https://github.com/tgstation/tgstation-server", - IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4" - }, - Color = Color.Gold, - Description = "TGS has begun deploying active repository code to production.", - Fields = fields, - Title = "Code Deployment", - Footer = new EmbedFooterBuilder - { - Text = $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}" - }, - Timestamp = estimatedCompletionTime - }; - - Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId); - if (!(client.GetChannel(channelId) is IMessageChannel channel)) - { - Logger.LogTrace("Channel ID {0} does not exist or is not an IMessageChannel!", channelId); - return (errorMessage, dreamMakerOutput) => Task.CompletedTask; - } - - var message = await channel.SendMessageAsync( - "DM: Deployment in Progress...", - false, - builder.Build(), - new RequestOptions - { - CancelToken = cancellationToken - }) - .ConfigureAwait(false); - - return async (errorMessage, dreamMakerOutput) => - { - var completionString = errorMessage == null ? "Succeeded" : "Failed"; - builder.Footer.Text = completionString; - builder.Color = errorMessage == null ? Color.Green : Color.Red; - builder.Timestamp = DateTimeOffset.UtcNow; - builder.Description = errorMessage == null - ? "The deployment completed successfully and will be available at the next server reboot." - : "The deployment failed."; - - var showDMOutput = outputDisplayType switch - { - DiscordDMOutputDisplayType.Always => true, - DiscordDMOutputDisplayType.Never => false, - DiscordDMOutputDisplayType.OnError => errorMessage != null, - _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"), + // isAdmin and Tag populated by manager + }, + FriendlyName = e.Author.Username, + Mention = NormalizeMentions(e.Author.Mention), + }, + Context = typingState, }; - if (dreamMakerOutput != null) - { - showDMOutput = showDMOutput && dreamMakerOutput.Length < EmbedFieldBuilder.MaxFieldValueLength - (6 + Environment.NewLine.Length); - if (showDMOutput) - builder.AddField(new EmbedFieldBuilder - { - Name = "DreamMaker Output", - Value = $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```" - }); - } - - if (errorMessage != null) - builder.AddField(new EmbedFieldBuilder - { - Name = "Error Message", - Value = errorMessage - }); - - var updatedMessage = $"DM: Deployment {completionString}!"; - try - { - await message.ModifyAsync( - props => - { - props.Content = updatedMessage; - props.Embed = builder.Build(); - }) - .ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Updating deploy embed {0} failed, attempting new post!", message.Id); - try - { - await channel.SendMessageAsync( - updatedMessage, - false, - builder.Build()) - .ConfigureAwait(false); - } - catch (Exception ex2) - { - Logger.LogWarning(ex2, "Posting completion deploy embed failed!"); - } - } - }; + EnqueueMessage(result); + typingState = null; + } + finally + { + typingState?.Dispose(); + } } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 4c2a969817..72b23baa3e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -1,13 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Chat.Providers { /// - /// For interacting with a chat service + /// For interacting with a chat service. /// interface IProvider : IAsyncDisposable { @@ -22,7 +23,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers bool Disposed { get; } /// - /// The that indicates the was mentioned + /// The that indicates the was mentioned. /// string BotMention { get; } @@ -32,9 +33,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Task InitialConnectionJob { get; } /// - /// Get a resulting in the next the recieves or on a disconnect + /// Get a resulting in the next the recieves or on a disconnect. /// - /// The for the operation + /// The for the operation. /// A resulting in the next available or if the needed to reconnect. /// Note that private messages will come in the form of s not returned in . Do not the on continuations run from the returned . Task NextMessage(CancellationToken cancellationToken); @@ -42,25 +43,25 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// Gracefully disconnects the provider. Permanently stops the reconnection timer. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Disconnect(CancellationToken cancellationToken); /// - /// Get the s for given + /// Get the s for given . /// - /// The s to map - /// The for the operation - /// A resulting in a of the s representing + /// The s to map. + /// The for the operation. + /// A resulting in a of the s representing . Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken); /// - /// Send a message to the + /// Send a message to the . /// - /// The to send to - /// The message contents - /// The for the operation - /// A representing the running operation + /// The to send to. + /// The message contents. + /// The for the operation. + /// A representing the running operation. Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken); /// @@ -79,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The optional the deployment is expected to be completed at. /// The repository GitHub owner, if any. /// The repository GitHub name, if any. - /// The to send to + /// The to send to. /// if the local deployment commit was pushed to the remote repository. /// The for the operation. /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProviderFactory.cs index 9350efd7c6..d79e0bb7d5 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProviderFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProviderFactory.cs @@ -1,17 +1,17 @@ -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Chat.Providers { /// - /// Factory for s + /// Factory for s. /// interface IProviderFactory { /// - /// Create a + /// Create a . /// - /// The containing settings for the new provider - /// A new + /// The containing settings for the new provider. + /// A new . IProvider CreateProvider(ChatBot settings); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index a554d12637..b504ddf0be 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -1,12 +1,14 @@ -using Meebey.SmartIrc4net; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Meebey.SmartIrc4net; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Extensions; @@ -17,7 +19,7 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Chat.Providers { /// - /// for internet relay chat + /// for internet relay chat. /// sealed class IrcProvider : Provider { @@ -33,71 +35,71 @@ namespace Tgstation.Server.Host.Components.Chat.Providers public override string BotMention => client.Nickname; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// The client + /// The client. /// readonly IrcFeatures client; /// - /// Address of the server to connect to + /// Address of the server to connect to. /// readonly string address; /// - /// Port of the server to connect to + /// Port of the server to connect to. /// readonly ushort port; /// - /// IRC nickname + /// IRC nickname. /// readonly string nickname; /// - /// Password which will used for authentication + /// Password which will used for authentication. /// readonly string password; /// - /// The of + /// The of . /// readonly IrcPasswordType? passwordType; /// - /// Map of s to channel names + /// Map of s to channel names. /// readonly Dictionary channelIdMap; /// - /// Map of s to query users + /// Map of s to query users. /// readonly Dictionary queryChannelIdMap; /// - /// Id counter for + /// Id counter for . /// ulong channelIdCounter; /// - /// The used for + /// The used for . /// Task listenTask; /// - /// If we are disconnecting + /// If we are disconnecting. /// bool disconnecting; /// - /// Construct an + /// Initializes a new instance of the class. /// /// The for the provider. - /// The to get the from - /// The value of + /// The to get the from. + /// The value of . /// The for the . /// The for the . public IrcProvider( @@ -136,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ActiveChannelSyncing = true, AutoNickHandling = true, CtcpVersion = assemblyInformationProvider.VersionString, - UseSsl = ircBuilder.UseSsl.Value + UseSsl = ircBuilder.UseSsl.Value, }; if (ircBuilder.UseSsl.Value) client.ValidateServerCertificate = true; // dunno if it defaults to that or what @@ -158,83 +160,167 @@ namespace Tgstation.Server.Host.Components.Chat.Providers await HardDisconnect(default).ConfigureAwait(false); } - /// - /// Handle an IRC message - /// - /// The - /// If this is a query message - void HandleMessage(IrcEventArgs e, bool isPrivate) - { - if (e.Data.Nick.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) - return; - - var username = e.Data.Nick; - var channelName = isPrivate ? username : e.Data.Channel; - - ulong MapAndGetChannelId(Dictionary dicToCheck) - { - ulong? resultId = null; - if (!dicToCheck.Any(x => + /// + public override Task> MapChannels( + IEnumerable channels, + CancellationToken cancellationToken) + => Task.Factory.StartNew( + () => { - if (x.Value != channelName) - return false; - resultId = x.Key; - return true; - })) - { - resultId = channelIdCounter++; - dicToCheck.Add(resultId.Value, channelName); - if (dicToCheck == queryChannelIdMap) - channelIdMap.Add(resultId.Value, null); - } - - return resultId.Value; - } - - ulong userId, channelId; - lock (client) - { - userId = MapAndGetChannelId(queryChannelIdMap); - channelId = isPrivate ? userId : MapAndGetChannelId(channelIdMap); - } - - var message = new Message - { - Content = e.Data.Message, - User = new ChatUser - { - Channel = new ChannelRepresentation + if (channels.Any(x => x.IrcChannel == null)) + throw new InvalidOperationException("ChatChannel missing IrcChannel!"); + lock (client) { - ConnectionName = address, - FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, - RealId = channelId, - IsPrivateChannel = isPrivate + var channelsWithKeys = new Dictionary(); + var hs = new HashSet(); // for unique inserts + foreach (var channel in channels) + { + var name = channel.GetIrcChannelName(); + var key = channel.GetIrcChannelKey(); + if (hs.Add(name) && key != null) + channelsWithKeys.Add(name, key); + } - // isAdmin and Tag populated by manager - }, - FriendlyName = username, - RealId = userId, - Mention = username + var toPart = new List(); + foreach (var activeChannel in client.JoinedChannels) + if (!hs.Remove(activeChannel)) + toPart.Add(activeChannel); + + foreach (var channelToLeave in toPart) + client.RfcPart(channelToLeave, "Pretty nice abscond!"); + foreach (var channelToJoin in hs) + if (channelsWithKeys.TryGetValue(channelToJoin, out var key)) + client.RfcJoin(channelToJoin, key); + else + client.RfcJoin(channelToJoin); + + return (IReadOnlyCollection)channels + .Select(x => + { + var channelName = x.GetIrcChannelName(); + ulong? id = null; + if (!channelIdMap.Any(y => + { + if (y.Value != channelName) + return false; + id = y.Key; + return true; + })) + { + id = channelIdCounter++; + channelIdMap.Add(id.Value, channelName); + } + + return new ChannelRepresentation + { + RealId = id.Value, + IsAdminChannel = x.IsAdminChannel == true, + ConnectionName = address, + FriendlyName = channelIdMap[id.Value], + IsPrivateChannel = false, + Tag = x.Tag, + }; + }) + .ToList(); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + // IRC doesn't allow newlines + message = String.Concat( + message + .Where(x => x != '\r') + .Select(x => x == '\n' ? '|' : x)); + + var channelName = channelIdMap[channelId]; + SendType sendType; + if (channelName == null) + { + channelName = queryChannelIdMap[channelId]; + sendType = SendType.Notice; } - }; + else + sendType = SendType.Message; + try + { + client.SendMessage(sendType, channelName, message); + } + catch (Exception e) + { + Logger.LogWarning(e, "Unable to send to channel {0}!", channelName); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); - EnqueueMessage(message); + /// + public override async Task> SendUpdateMessage( + Models.RevisionInformation revisionInformation, + Version byondVersion, + DateTimeOffset? estimatedCompletionTime, + string gitHubOwner, + string gitHubRepo, + ulong channelId, + bool localCommitPushed, + CancellationToken cancellationToken) + { + var commitInsert = revisionInformation.CommitSha.Substring(0, 7); + string remoteCommitInsert; + if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha) + { + commitInsert = String.Format(CultureInfo.InvariantCulture, localCommitPushed ? "^{0}" : "{0}", commitInsert); + remoteCommitInsert = String.Empty; + } + else + remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7)); + + var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0 + ? String.Empty + : String.Format( + CultureInfo.InvariantCulture, + " (Test Merges: {0})", + String.Join( + ", ", + revisionInformation + .ActiveTestMerges + .Select(x => x.TestMerge) + .Select(x => + { + var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha.Substring(0, 7)); + if (x.Comment != null) + result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment); + return result; + }))); + + await SendMessage( + channelId, + String.Format( + CultureInfo.InvariantCulture, + "DM: Deploying revision: {0}{1}{2} BYOND Version: {3}{4}", + commitInsert, + testmergeInsert, + remoteCommitInsert, + byondVersion.Build > 0 + ? byondVersion.ToString() + : $"{byondVersion.Major}.{byondVersion.Minor}", + estimatedCompletionTime.HasValue + ? $" ETA: {estimatedCompletionTime - DateTimeOffset.UtcNow}" + : String.Empty), + cancellationToken).ConfigureAwait(false); + + return (errorMessage, dreamMakerOutput) => SendMessage( + channelId, + $"DM: Deployment {(errorMessage == null ? "complete" : "failed")}!", + cancellationToken); } - /// - /// When a query message is received in IRC - /// - /// The sender of the event - /// The - void Client_OnQueryMessage(object sender, IrcEventArgs e) => HandleMessage(e, true); - - /// - /// When a channel message is received in IRC - /// - /// The sender of the event - /// The - void Client_OnChannelMessage(object sender, IrcEventArgs e) => HandleMessage(e, false); - /// protected override async Task Connect(CancellationToken cancellationToken) { @@ -322,6 +408,116 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } } + /// + protected override async Task DisconnectImpl(CancellationToken cancellationToken) + { + try + { + await Task.Factory.StartNew( + () => + { + try + { + client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through + } + catch (Exception e) + { + Logger.LogWarning(e, "Error quitting IRC!"); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); + await HardDisconnect(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.LogWarning(e, "Error disconnecting from IRC!"); + } + } + + /// + /// Handle an IRC message. + /// + /// The . + /// If this is a query message. + void HandleMessage(IrcEventArgs e, bool isPrivate) + { + if (e.Data.Nick.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) + return; + + var username = e.Data.Nick; + var channelName = isPrivate ? username : e.Data.Channel; + + ulong MapAndGetChannelId(Dictionary dicToCheck) + { + ulong? resultId = null; + if (!dicToCheck.Any(x => + { + if (x.Value != channelName) + return false; + resultId = x.Key; + return true; + })) + { + resultId = channelIdCounter++; + dicToCheck.Add(resultId.Value, channelName); + if (dicToCheck == queryChannelIdMap) + channelIdMap.Add(resultId.Value, null); + } + + return resultId.Value; + } + + ulong userId, channelId; + lock (client) + { + userId = MapAndGetChannelId(queryChannelIdMap); + channelId = isPrivate ? userId : MapAndGetChannelId(channelIdMap); + } + + var message = new Message + { + Content = e.Data.Message, + User = new ChatUser + { + Channel = new ChannelRepresentation + { + ConnectionName = address, + FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, + RealId = channelId, + IsPrivateChannel = isPrivate, + + // isAdmin and Tag populated by manager + }, + FriendlyName = username, + RealId = userId, + Mention = username, + }, + }; + + EnqueueMessage(message); + } + + /// + /// When a query message is received in IRC. + /// + /// The sender of the event. + /// The . + void Client_OnQueryMessage(object sender, IrcEventArgs e) => HandleMessage(e, true); + + /// + /// When a channel message is received in IRC. + /// + /// The sender of the event. + /// The . + void Client_OnChannelMessage(object sender, IrcEventArgs e) => HandleMessage(e, false); + /// /// Perform a non-blocking . /// @@ -414,39 +610,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.WriteLine("CAP END", Priority.Critical); } - /// - protected override async Task DisconnectImpl(CancellationToken cancellationToken) - { - try - { - await Task.Factory.StartNew( - () => - { - try - { - client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through - } - catch (Exception e) - { - Logger.LogWarning(e, "Error quitting IRC!"); - } - }, - cancellationToken, - DefaultIOManager.BlockingTaskCreationOptions, - TaskScheduler.Current) - .ConfigureAwait(false); - await HardDisconnect(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - Logger.LogWarning(e, "Error disconnecting from IRC!"); - } - } - + /// + /// Attempt to disconnect from IRC immediately. + /// + /// The for the operation. + /// A representing the running operation. async Task HardDisconnect(CancellationToken cancellationToken) { if (!Connected) @@ -484,149 +652,5 @@ namespace Tgstation.Server.Host.Components.Chat.Providers asyncDelayer.Delay(TimeSpan.FromSeconds(TimeoutSeconds), cancellationToken)) .ConfigureAwait(false); } - - /// - public override Task> MapChannels( - IEnumerable channels, - CancellationToken cancellationToken) - => Task.Factory.StartNew(() => - { - if (channels.Any(x => x.IrcChannel == null)) - throw new InvalidOperationException("ChatChannel missing IrcChannel!"); - lock (client) - { - var channelsWithKeys = new Dictionary(); - var hs = new HashSet(); // for unique inserts - foreach (var channel in channels) - { - var name = channel.GetIrcChannelName(); - var key = channel.GetIrcChannelKey(); - if (hs.Add(name) && key != null) - channelsWithKeys.Add(name, key); - } - - var toPart = new List(); - foreach (var activeChannel in client.JoinedChannels) - if (!hs.Remove(activeChannel)) - toPart.Add(activeChannel); - - foreach (var channelToLeave in toPart) - client.RfcPart(channelToLeave, "Pretty nice abscond!"); - foreach (var channelToJoin in hs) - if (channelsWithKeys.TryGetValue(channelToJoin, out var key)) - client.RfcJoin(channelToJoin, key); - else - client.RfcJoin(channelToJoin); - - return (IReadOnlyCollection)channels - .Select(x => - { - var channelName = x.GetIrcChannelName(); - ulong? id = null; - if (!channelIdMap.Any(y => - { - if (y.Value != channelName) - return false; - id = y.Key; - return true; - })) - { - id = channelIdCounter++; - channelIdMap.Add(id.Value, channelName); - } - - return new ChannelRepresentation - { - RealId = id.Value, - IsAdminChannel = x.IsAdminChannel == true, - ConnectionName = address, - FriendlyName = channelIdMap[id.Value], - IsPrivateChannel = false, - Tag = x.Tag - }; - }) - .ToList(); - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); - - /// - public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - // IRC doesn't allow newlines - message = String.Concat( - message - .Where(x => x != '\r') - .Select(x => x == '\n' ? '|' : x)); - - var channelName = channelIdMap[channelId]; - SendType sendType; - if (channelName == null) - { - channelName = queryChannelIdMap[channelId]; - sendType = SendType.Notice; - } - else - sendType = SendType.Message; - try - { - client.SendMessage(sendType, channelName, message); - } - catch (Exception e) - { - Logger.LogWarning(e, "Unable to send to channel {0}!", channelName); - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); - - /// - public override async Task> SendUpdateMessage( - Models.RevisionInformation revisionInformation, - Version byondVersion, - DateTimeOffset? estimatedCompletionTime, - string gitHubOwner, - string gitHubRepo, - ulong channelId, - bool localCommitPushed, - CancellationToken cancellationToken) - { - var commitInsert = revisionInformation.CommitSha.Substring(0, 7); - string remoteCommitInsert; - if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha) - { - commitInsert = String.Format(CultureInfo.InvariantCulture, localCommitPushed ? "^{0}" : "{0}", commitInsert); - remoteCommitInsert = String.Empty; - } - else - remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7)); - - var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})", - String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => - { - var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha.Substring(0, 7)); - if (x.Comment != null) - result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment); - return result; - }))); - - await SendMessage( - channelId, - String.Format( - CultureInfo.InvariantCulture, - "DM: Deploying revision: {0}{1}{2} BYOND Version: {3}{4}", - commitInsert, - testmergeInsert, - remoteCommitInsert, - byondVersion.Build > 0 - ? byondVersion.ToString() - : $"{byondVersion.Major}.{byondVersion.Minor}", - estimatedCompletionTime.HasValue - ? $" ETA: {estimatedCompletionTime - DateTimeOffset.UtcNow}" - : String.Empty), - cancellationToken).ConfigureAwait(false); - - return (errorMessage, dreamMakerOutput) => SendMessage( - channelId, - $"DM: Deployment {(errorMessage == null ? "complete" : "failed")}!", - cancellationToken); - } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs index 7758ff8802..76dea0d006 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; @@ -32,12 +34,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers readonly IJobManager jobManager; /// - /// of received s + /// of received s. /// readonly Queue messageQueue; /// - /// The backing for + /// The backing for . /// readonly TaskCompletionSource initialConnectionTcs; @@ -47,22 +49,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers readonly object reconnectTaskLock; /// - /// that completes while isn't empty + /// that completes while isn't empty. /// TaskCompletionSource nextMessage; /// - /// The auto reconnect + /// The auto reconnect . /// Task reconnectTask; /// - /// for + /// for . /// CancellationTokenSource reconnectCts; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -90,19 +92,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public bool Disposed { get; private set; } - /// - /// Queues a for - /// - /// The to queue - protected void EnqueueMessage(Message message) - { - lock (messageQueue) - { - messageQueue.Enqueue(message); - nextMessage.TrySetResult(null); - } - } - /// public virtual async ValueTask DisposeAsync() { @@ -111,20 +100,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Logger.LogTrace("Disposed"); } - /// - /// Attempt to connect the . - /// - /// The for the operation. - /// A representing the running operation. - protected abstract Task Connect(CancellationToken cancellationToken); - - /// - /// Gracefully disconnects the provider. - /// - /// The for the operation. - /// A representing the running operation. - protected abstract Task DisconnectImpl(CancellationToken cancellationToken); - /// public async Task Disconnect(CancellationToken cancellationToken) { @@ -157,6 +132,64 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } } + /// + public Task SetReconnectInterval(uint reconnectInterval, bool connectNow) + { + if (reconnectInterval == 0) + throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!"); + + Task stopOldTimerTask; + lock (reconnectTaskLock) + { + stopOldTimerTask = StopReconnectionTimer(); + reconnectCts = new CancellationTokenSource(); + reconnectTask = ReconnectionLoop(reconnectInterval, connectNow, reconnectCts.Token); + } + + return stopOldTimerTask; + } + + /// + public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken); + + /// + public abstract Task> SendUpdateMessage( + RevisionInformation revisionInformation, + Version byondVersion, + DateTimeOffset? estimatedCompletionTime, + string gitHubOwner, + string gitHubRepo, + ulong channelId, + bool localCommitPushed, + CancellationToken cancellationToken); + + /// + /// Attempt to connect the . + /// + /// The for the operation. + /// A representing the running operation. + protected abstract Task Connect(CancellationToken cancellationToken); + + /// + /// Gracefully disconnects the provider. + /// + /// The for the operation. + /// A representing the running operation. + protected abstract Task DisconnectImpl(CancellationToken cancellationToken); + + /// + /// Queues a for . + /// + /// The to queue. + protected void EnqueueMessage(Message message) + { + lock (messageQueue) + { + messageQueue.Enqueue(message); + nextMessage.TrySetResult(null); + } + } + /// /// Stops and awaits the . /// @@ -177,23 +210,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return Task.CompletedTask; } - /// - public Task SetReconnectInterval(uint reconnectInterval, bool connectNow) - { - if (reconnectInterval == 0) - throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!"); - - Task stopOldTimerTask; - lock (reconnectTaskLock) - { - stopOldTimerTask = StopReconnectionTimer(); - reconnectCts = new CancellationTokenSource(); - reconnectTask = ReconnectionLoop(reconnectInterval, connectNow, reconnectCts.Token); - } - - return stopOldTimerTask; - } - /// /// Creates a that will attempt to reconnect the every minutes. /// @@ -218,7 +234,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Description = $"Reconnect chat bot: {ChatBot.Name}", CancelRight = (ulong)ChatBotRights.WriteEnabled, CancelRightsType = RightsType.ChatBots, - Instance = ChatBot.Instance + Instance = ChatBot.Instance, }; await jobManager.RegisterOperation( @@ -256,7 +272,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { break; } - catch(Exception e) + catch (Exception e) { Logger.LogError(e, "Error reconnecting!"); } @@ -267,19 +283,5 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } while (true); } - - /// - public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken); - - /// - public abstract Task> SendUpdateMessage( - RevisionInformation revisionInformation, - Version byondVersion, - DateTimeOffset? estimatedCompletionTime, - string gitHubOwner, - string gitHubRepo, - ulong channelId, - bool localCommitPushed, - CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs index bdf1bbc0d0..2d3893ef6d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs @@ -1,6 +1,8 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Globalization; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Jobs; @@ -12,12 +14,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers sealed class ProviderFactory : IProviderFactory { /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; @@ -27,17 +29,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers readonly IJobManager jobManager; /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . public ProviderFactory( IJobManager jobManager, IAssemblyInformationProvider assemblyInformationProvider, diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index 8dbf155b5f..d8830a48f9 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -1,11 +1,13 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Deployment.Remote; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; @@ -14,7 +16,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// Standard + /// Standard . /// sealed class DmbFactory : IDmbFactory, ICompileJobSink { @@ -32,12 +34,12 @@ namespace Tgstation.Server.Host.Components.Deployment public bool DmbAvailable => nextDmbProvider != null; /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; @@ -47,17 +49,17 @@ namespace Tgstation.Server.Host.Components.Deployment readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly Api.Models.Instance metadata; /// - /// The for + /// The for . /// readonly CancellationTokenSource cleanupCts; @@ -67,17 +69,17 @@ namespace Tgstation.Server.Host.Components.Deployment readonly IDictionary jobLockCounts; /// - /// representing calls to + /// representing calls to . /// Task cleanupTask; /// - /// resulting in the latest yet to exist + /// resulting in the latest yet to exist. /// TaskCompletionSource newerDmbTcs; /// - /// The latest + /// The latest . /// IDmbProvider nextDmbProvider; @@ -87,13 +89,13 @@ namespace Tgstation.Server.Host.Components.Deployment bool started; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . /// The value of . - /// The value of - /// The value of + /// The value of . + /// The value of . public DmbFactory( IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, @@ -116,39 +118,6 @@ namespace Tgstation.Server.Host.Components.Deployment /// public void Dispose() => cleanupCts.Dispose(); // we don't dispose nextDmbProvider here, since it might be the only thing we have - /// - /// Delete the of - /// - /// The to clean - void CleanJob(CompileJob job) - { - async Task HandleCleanup() - { - var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token); - var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager( - metadata, - job); - - // DCT: None available - var deploymentJob = remoteDeploymentManager.MarkInactive(job, default); - var otherTask = cleanupTask; - await Task.WhenAll(otherTask, deleteJob, deploymentJob).ConfigureAwait(false); - } - - lock (jobLockCounts) - if (!jobLockCounts.TryGetValue(job.Id.Value, out var currentVal) || currentVal == 1) - { - jobLockCounts.Remove(job.Id.Value); - logger.LogDebug("Cleaning lock-free compile job {0} => {1}", job.Id, job.DirectoryName); - cleanupTask = HandleCleanup(); - } - else - { - var decremented = --jobLockCounts[job.Id.Value]; - logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented); - } - } - /// public async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken) { @@ -238,7 +207,7 @@ namespace Tgstation.Server.Host.Components.Deployment } /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public async Task FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken) { if (compileJob == null) @@ -344,10 +313,10 @@ namespace Tgstation.Server.Host.Components.Deployment newProvider.Dispose(); } } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public async Task CleanUnusedCompileJobs(CancellationToken cancellationToken) { List jobIdsToSkip; @@ -406,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Deployment if (deleting > 0) await Task.WhenAll(tasks).ConfigureAwait(false); } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// public CompileJob LatestCompileJob() @@ -415,5 +384,38 @@ namespace Tgstation.Server.Host.Components.Deployment return null; return LockNextDmb(0)?.CompileJob; } + + /// + /// Delete the of . + /// + /// The to clean. + void CleanJob(CompileJob job) + { + async Task HandleCleanup() + { + var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token); + var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager( + metadata, + job); + + // DCT: None available + var deploymentJob = remoteDeploymentManager.MarkInactive(job, default); + var otherTask = cleanupTask; + await Task.WhenAll(otherTask, deleteJob, deploymentJob).ConfigureAwait(false); + } + + lock (jobLockCounts) + if (!jobLockCounts.TryGetValue(job.Id.Value, out var currentVal) || currentVal == 1) + { + jobLockCounts.Remove(job.Id.Value); + logger.LogDebug("Cleaning lock-free compile job {0} => {1}", job.Id, job.DirectoryName); + cleanupTask = HandleCleanup(); + } + else + { + var decremented = --jobLockCounts[job.Id.Value]; + logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented); + } + } } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs index 621ea757a4..f3711ede4f 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs @@ -1,4 +1,5 @@ using System; + using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; @@ -14,32 +15,32 @@ namespace Tgstation.Server.Host.Components.Deployment public string Directory => ioManager.ResolvePath(CompileJob.DirectoryName.ToString() + directoryAppend); /// - /// The for the + /// The for the . /// public CompileJob CompileJob { get; } /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// Extra path to add to the end of + /// Extra path to add to the end of . /// readonly string directoryAppend; /// - /// The to run when is called + /// The to run when is called. /// Action onDispose; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The optional value of + /// The value of . + /// The value of . + /// The value of . + /// The optional value of . public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose, string directoryAppend = null) { CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob)); diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index a028bc497d..eaf3c1b3ac 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -1,11 +1,13 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; @@ -27,47 +29,47 @@ namespace Tgstation.Server.Host.Components.Deployment sealed class DreamMaker : IDreamMaker { /// - /// Extension for .dmbs + /// Extension for .dmbs. /// public const string DmbExtension = ".dmb"; /// - /// Extension for .dmes + /// Extension for .dmes. /// const string DmeExtension = "dme"; /// - /// The for + /// The for . /// readonly IByondManager byond; /// - /// The for + /// The for . /// readonly IIOManager ioManager; /// - /// The for + /// The for . /// readonly StaticFiles.IConfiguration configuration; /// - /// The for + /// The for . /// readonly ISessionControllerFactory sessionControllerFactory; /// - /// The for + /// The for . /// readonly IEventConsumer eventConsumer; /// - /// The for + /// The for . /// readonly IChatManager chatManager; /// - /// The for + /// The for . /// readonly IProcessExecutor processExecutor; @@ -87,7 +89,7 @@ namespace Tgstation.Server.Host.Components.Deployment readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory; /// - /// The for + /// The for . /// readonly ILogger logger; @@ -112,7 +114,7 @@ namespace Tgstation.Server.Host.Components.Deployment string currentDreamMakerOutput; /// - /// If a compile job is running + /// If a compile job is running. /// bool deploying; @@ -127,19 +129,19 @@ namespace Tgstation.Server.Host.Components.Deployment : exception.Message; /// - /// Construct + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . - /// The value of + /// The value of . /// The value of . public DreamMaker( IByondManager byond, @@ -171,354 +173,8 @@ namespace Tgstation.Server.Host.Components.Deployment deploymentLock = new object(); } - /// - /// Gradually triggers a given over a given - /// - /// The to report progress - /// A representing the duration to give progress over - /// The for the operation - /// A representing the running operation - async Task ProgressTask(Action progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken) - { - progressReporter(0); - var sleepInterval = estimatedDuration / 100; - - logger.LogDebug("Compile is expected to take: {0}", estimatedDuration); - try - { - for (var I = 0; I < 99; ++I) - { - await Task.Delay(sleepInterval, cancellationToken).ConfigureAwait(false); - progressReporter(I + 1); - } - } - catch (OperationCanceledException) { } - } - - /// - /// Run a quick DD instance to test the DMAPI is installed on the target code - /// - /// The timeout in seconds for validation - /// The level to use to validate the API - /// The for the operation - /// The current - /// The port to use for API validation - /// If the API validation is required to complete the deployment. - /// The for the operation - /// A representing the running operation - async Task VerifyApi( - uint timeout, - DreamDaemonSecurity securityLevel, - Models.CompileJob job, - IByondExecutableLock byondLock, - ushort portToUse, - bool requireValidate, - CancellationToken cancellationToken) - { - logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty); - var launchParameters = new DreamDaemonLaunchParameters - { - AllowWebClient = false, - Port = portToUse, - SecurityLevel = securityLevel, - StartupTimeout = timeout, - TopicRequestTimeout = 0, // not used - HeartbeatSeconds = 0 // not used - }; - - job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider - - ApiValidationStatus validationStatus; - using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job)) - await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false)) - { - var launchResult = await controller.LaunchResult.ConfigureAwait(false); - - if (launchResult.StartupTime.HasValue) - await controller.Lifetime.WithToken(cancellationToken).ConfigureAwait(false); - - if (!controller.Lifetime.IsCompleted) - await controller.DisposeAsync().ConfigureAwait(false); - - validationStatus = controller.ApiValidationStatus; - - if (requireValidate && validationStatus == ApiValidationStatus.NeverValidated) - throw new JobException(ErrorCode.DreamMakerNeverValidated); - - logger.LogTrace("API validation status: {0}", validationStatus); - - job.DMApiVersion = controller.DMApiVersion; - } - - switch (validationStatus) - { - case ApiValidationStatus.RequiresUltrasafe: - job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; - return; - case ApiValidationStatus.RequiresSafe: - job.MinimumSecurityLevel = DreamDaemonSecurity.Safe; - return; - case ApiValidationStatus.RequiresTrusted: - job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted; - return; - case ApiValidationStatus.NeverValidated: - if (requireValidate) - throw new JobException(ErrorCode.DreamMakerNeverValidated); - job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; - break; - case ApiValidationStatus.BadValidationRequest: - case ApiValidationStatus.Incompatible: - throw new JobException(ErrorCode.DreamMakerInvalidValidation); - case ApiValidationStatus.UnaskedValidationRequest: - default: - throw new InvalidOperationException( - $"Session controller returned unexpected ApiValidationStatus: {validationStatus}"); - } - } - - /// - /// Compiles a .dme with DreamMaker - /// - /// The path to the DreamMaker executable - /// The for the operation - /// The for the operation - /// A representing the running operation - async Task RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken) - { - using var dm = processExecutor.LaunchProcess( - dreamMakerPath, - ioManager.ResolvePath( - job.DirectoryName.ToString()), - $"-clean {job.DmeName}.{DmeExtension}", - true, - true, - true); - int exitCode; - using (cancellationToken.Register(() => dm.Terminate())) - exitCode = await dm.Lifetime.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - - logger.LogDebug("DreamMaker exit code: {0}", exitCode); - job.Output = await dm.GetCombinedOutput(cancellationToken).ConfigureAwait(false); - currentDreamMakerOutput = job.Output; - logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output); - return exitCode; - } - - /// - /// Adds server side includes to the .dme being compiled - /// - /// The for the operation - /// The for the operation - /// A representing the running operation - async Task ModifyDme(Models.CompileJob job, CancellationToken cancellationToken) - { - var dmeFileName = String.Join('.', job.DmeName, DmeExtension); - var dmePath = ioManager.ConcatPath(job.DirectoryName.ToString(), dmeFileName); - var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken); - - var dmeModificationsTask = configuration.CopyDMFilesTo(dmeFileName, ioManager.ResolvePath(job.DirectoryName.ToString()), cancellationToken); - - var dmeBytes = await dmeReadTask.ConfigureAwait(false); - var dme = Encoding.UTF8.GetString(dmeBytes); - - var dmeModifications = await dmeModificationsTask.ConfigureAwait(false); - - if (dmeModifications == null || dmeModifications.TotalDmeOverwrite) - { - if (dmeModifications != null) - logger.LogDebug(".dme replacement configured!"); - else - logger.LogTrace("No .dme modifications required."); - return; - } - - var dmeLines = new List(dme.Split('\n', StringSplitOptions.None)); - for (var I = 0; I < dmeLines.Count; ++I) - { - var line = dmeLines[I]; - if (line.Contains("BEGIN_INCLUDE", StringComparison.Ordinal) && dmeModifications.HeadIncludeLine != null) - { - var headIncludeLineNumber = I + 1; - logger.LogDebug( - "Inserting HeadInclude.dm at line {0}: {1}", - headIncludeLineNumber, - dmeModifications.HeadIncludeLine); - dmeLines.Insert(headIncludeLineNumber, dmeModifications.HeadIncludeLine); - ++I; - } - else if (line.Contains("END_INCLUDE", StringComparison.Ordinal) && dmeModifications.TailIncludeLine != null) - { - logger.LogDebug( - "Inserting TailInclude.dm at line {0}: {1}", - I, - dmeModifications.TailIncludeLine); - dmeLines.Insert(I, dmeModifications.TailIncludeLine); - break; - } - } - - dmeBytes = Encoding.UTF8.GetBytes(String.Join(Environment.NewLine, dmeLines)); - await ioManager.WriteAllBytes(dmePath, dmeBytes, cancellationToken).ConfigureAwait(false); - } - - /// - /// Cleans up a failed compile . - /// - /// The running . - /// The associated with the . - /// The that was thrown. - /// A representing the running operation - async Task CleanupFailedCompile(Models.CompileJob job, IRemoteDeploymentManager remoteDeploymentManager, Exception exception) - { - async Task CleanDir() - { - logger.LogTrace("Cleaning compile directory..."); - var jobPath = job.DirectoryName.ToString(); - try - { - // DCT: None available - await ioManager.DeleteDirectory(jobPath, default).ConfigureAwait(false); - } - catch (Exception e) - { - logger.LogWarning(e, "Error cleaning up compile directory {0}!", ioManager.ResolvePath(jobPath)); - } - } - - // DCT: None available - await Task.WhenAll( - CleanDir(), - remoteDeploymentManager.FailDeployment( - job, - FormatExceptionForUsers(exception), - default)) - .ConfigureAwait(false); - } - - /// - /// Executes and populate a given - /// - /// The to run and populate - /// The to use - /// The to use - /// The to use - /// The to use. - /// The timeout for validating the DMAPI - /// The for the operation - /// A representing the running operation - async Task RunCompileJob( - Models.CompileJob job, - Api.Models.Internal.DreamMakerSettings dreamMakerSettings, - IByondExecutableLock byondLock, - IRepository repository, - IRemoteDeploymentManager remoteDeploymentManager, - uint apiValidateTimeout, - CancellationToken cancellationToken) - { - var outputDirectory = job.DirectoryName.ToString(); - logger.LogTrace("Compile output GUID: {0}", outputDirectory); - - try - { - // copy the repository - logger.LogTrace("Copying repository to game directory..."); - var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory); - var repoOrigin = repository.Origin; - using (repository) - await repository.CopyTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false); - - // repository closed now - - // run precompile scripts - await eventConsumer.HandleEvent( - EventType.CompileStart, - new List - { - resolvedOutputDirectory, - repoOrigin.ToString() - }, - cancellationToken) - .ConfigureAwait(false); - - // determine the dme - if (job.DmeName == null) - { - logger.LogTrace("Searching for available .dmes..."); - var foundPaths = await ioManager.GetFilesWithExtension(resolvedOutputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false); - var foundPath = foundPaths.FirstOrDefault(); - if (foundPath == default) - throw new JobException(ErrorCode.DreamMakerNoDme); - job.DmeName = foundPath.Substring( - resolvedOutputDirectory.Length + 1, - foundPath.Length - resolvedOutputDirectory.Length - DmeExtension.Length - 2); // +1 for . in extension - } - else - { - var targetDme = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension)); - var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false); - if (!targetDmeExists) - throw new JobException(ErrorCode.DreamMakerMissingDme); - } - - logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName); - - await ModifyDme(job, cancellationToken).ConfigureAwait(false); - - // run compiler - var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); - - // verify api - try - { - if (exitCode != 0) - throw new JobException( - ErrorCode.DreamMakerExitCode, - new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}")); - - await VerifyApi( - apiValidateTimeout, - dreamMakerSettings.ApiValidationSecurityLevel.Value, - job, - byondLock, - dreamMakerSettings.ApiValidationPort.Value, - dreamMakerSettings.RequireDMApiValidation.Value, - cancellationToken) - .ConfigureAwait(false); - } - catch (JobException) - { - // DD never validated or compile failed - await eventConsumer.HandleEvent( - EventType.CompileFailure, - new List - { - resolvedOutputDirectory, - exitCode == 0 ? "1" : "0" - }, - cancellationToken) - .ConfigureAwait(false); - throw; - } - - await eventConsumer.HandleEvent(EventType.CompileComplete, new List { resolvedOutputDirectory }, cancellationToken).ConfigureAwait(false); - - logger.LogTrace("Applying static game file symlinks..."); - - // symlink in the static data - await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false); - - logger.LogDebug("Compile complete!"); - } - catch (Exception ex) - { - await CleanupFailedCompile(job, remoteDeploymentManager, ex).ConfigureAwait(false); - throw; - } - } - /// - #pragma warning disable CA1506 +#pragma warning disable CA1506 public async Task DeploymentProcess( Models.Job job, IDatabaseContextFactory databaseContextFactory, @@ -589,7 +245,7 @@ namespace Tgstation.Server.Host.Components.Deployment AccessToken = x.AccessToken, ShowTestMergeCommitters = x.ShowTestMergeCommitters, PushTestMergeCommits = x.PushTestMergeCommits, - PostTestMergeComment = x.PostTestMergeComment + PostTestMergeComment = x.PostTestMergeComment, }) .FirstOrDefaultAsync(cancellationToken) .ConfigureAwait(false); @@ -627,9 +283,9 @@ namespace Tgstation.Server.Host.Components.Deployment OriginCommitSha = repoSha, Instance = new Models.Instance { - Id = metadata.Id + Id = metadata.Id, }, - ActiveTestMerges = new List() + ActiveTestMerges = new List(), }; logger.LogInformation(Repository.Repository.OriginTrackingErrorTemplate, repoSha); @@ -671,12 +327,12 @@ namespace Tgstation.Server.Host.Components.Deployment var fullJob = compileJob.Job; compileJob.Job = new Models.Job { - Id = job.Id + Id = job.Id, }; var fullRevInfo = compileJob.RevisionInformation; compileJob.RevisionInformation = new Models.RevisionInformation { - Id = revInfo.Id + Id = revInfo.Id, }; databaseContext.Jobs.Attach(compileJob.Job); @@ -749,7 +405,7 @@ namespace Tgstation.Server.Host.Components.Deployment deploying = false; } } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// /// Calculate the average length of a deployment using a given . @@ -768,7 +424,7 @@ namespace Tgstation.Server.Host.Components.Deployment .Select(x => new Models.Job { StoppedAt = x.Job.StoppedAt, - StartedAt = x.Job.StartedAt + StartedAt = x.Job.StartedAt, }) .ToListAsync(cancellationToken) .ConfigureAwait(false); @@ -777,14 +433,27 @@ namespace Tgstation.Server.Host.Components.Deployment if (previousCompileJobs.Count != 0) { var totalSpan = TimeSpan.Zero; - foreach (var I in previousCompileJobs) - totalSpan += I.StoppedAt.Value - I.StartedAt.Value; + foreach (var previousCompileJob in previousCompileJobs) + totalSpan += previousCompileJob.StoppedAt.Value - previousCompileJob.StartedAt.Value; averageSpan = totalSpan / previousCompileJobs.Count; } return averageSpan; } + /// + /// Run the compile implementation. + /// + /// The . + /// The . + /// The API validation timeout. + /// The . + /// The . + /// The progress reporting . + /// The optional estimated of the compilation. + /// Whether or not the 's current commit exists on the remote repository. + /// The for the operation. + /// A resulting in the completed . async Task Compile( Models.RevisionInformation revisionInformation, Api.Models.Internal.DreamMakerSettings dreamMakerSettings, @@ -850,5 +519,353 @@ namespace Tgstation.Server.Host.Components.Deployment await progressTask.ConfigureAwait(false); } } + + /// + /// Executes and populate a given . + /// + /// The to run and populate. + /// The to use. + /// The to use. + /// The to use. + /// The to use. + /// The timeout for validating the DMAPI. + /// The for the operation. + /// A representing the running operation. + async Task RunCompileJob( + Models.CompileJob job, + Api.Models.Internal.DreamMakerSettings dreamMakerSettings, + IByondExecutableLock byondLock, + IRepository repository, + IRemoteDeploymentManager remoteDeploymentManager, + uint apiValidateTimeout, + CancellationToken cancellationToken) + { + var outputDirectory = job.DirectoryName.ToString(); + logger.LogTrace("Compile output GUID: {0}", outputDirectory); + + try + { + // copy the repository + logger.LogTrace("Copying repository to game directory..."); + var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory); + var repoOrigin = repository.Origin; + using (repository) + await repository.CopyTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false); + + // repository closed now + + // run precompile scripts + await eventConsumer.HandleEvent( + EventType.CompileStart, + new List + { + resolvedOutputDirectory, + repoOrigin.ToString(), + }, + cancellationToken) + .ConfigureAwait(false); + + // determine the dme + if (job.DmeName == null) + { + logger.LogTrace("Searching for available .dmes..."); + var foundPaths = await ioManager.GetFilesWithExtension(resolvedOutputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false); + var foundPath = foundPaths.FirstOrDefault(); + if (foundPath == default) + throw new JobException(ErrorCode.DreamMakerNoDme); + job.DmeName = foundPath.Substring( + resolvedOutputDirectory.Length + 1, + foundPath.Length - resolvedOutputDirectory.Length - DmeExtension.Length - 2); // +1 for . in extension + } + else + { + var targetDme = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension)); + var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false); + if (!targetDmeExists) + throw new JobException(ErrorCode.DreamMakerMissingDme); + } + + logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName); + + await ModifyDme(job, cancellationToken).ConfigureAwait(false); + + // run compiler + var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + // verify api + try + { + if (exitCode != 0) + throw new JobException( + ErrorCode.DreamMakerExitCode, + new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}")); + + await VerifyApi( + apiValidateTimeout, + dreamMakerSettings.ApiValidationSecurityLevel.Value, + job, + byondLock, + dreamMakerSettings.ApiValidationPort.Value, + dreamMakerSettings.RequireDMApiValidation.Value, + cancellationToken) + .ConfigureAwait(false); + } + catch (JobException) + { + // DD never validated or compile failed + await eventConsumer.HandleEvent( + EventType.CompileFailure, + new List + { + resolvedOutputDirectory, + exitCode == 0 ? "1" : "0", + }, + cancellationToken) + .ConfigureAwait(false); + throw; + } + + await eventConsumer.HandleEvent(EventType.CompileComplete, new List { resolvedOutputDirectory }, cancellationToken).ConfigureAwait(false); + + logger.LogTrace("Applying static game file symlinks..."); + + // symlink in the static data + await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false); + + logger.LogDebug("Compile complete!"); + } + catch (Exception ex) + { + await CleanupFailedCompile(job, remoteDeploymentManager, ex).ConfigureAwait(false); + throw; + } + } + + /// + /// Gradually triggers a given over a given . + /// + /// The to report progress. + /// A representing the duration to give progress over. + /// The for the operation. + /// A representing the running operation. + async Task ProgressTask(Action progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken) + { + progressReporter(0); + var sleepInterval = estimatedDuration / 100; + + logger.LogDebug("Compile is expected to take: {0}", estimatedDuration); + try + { + for (var iteration = 0; iteration < 99; ++iteration) + { + await Task.Delay(sleepInterval, cancellationToken).ConfigureAwait(false); + progressReporter(iteration + 1); + } + } + catch (OperationCanceledException) + { + } + } + + /// + /// Run a quick DD instance to test the DMAPI is installed on the target code. + /// + /// The timeout in seconds for validation. + /// The level to use to validate the API. + /// The for the operation. + /// The current . + /// The port to use for API validation. + /// If the API validation is required to complete the deployment. + /// The for the operation. + /// A representing the running operation. + async Task VerifyApi( + uint timeout, + DreamDaemonSecurity securityLevel, + Models.CompileJob job, + IByondExecutableLock byondLock, + ushort portToUse, + bool requireValidate, + CancellationToken cancellationToken) + { + logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty); + var launchParameters = new DreamDaemonLaunchParameters + { + AllowWebClient = false, + Port = portToUse, + SecurityLevel = securityLevel, + StartupTimeout = timeout, + TopicRequestTimeout = 0, // not used + HeartbeatSeconds = 0, // not used + }; + + job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider + + ApiValidationStatus validationStatus; + using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job)) + await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false)) + { + var launchResult = await controller.LaunchResult.ConfigureAwait(false); + + if (launchResult.StartupTime.HasValue) + await controller.Lifetime.WithToken(cancellationToken).ConfigureAwait(false); + + if (!controller.Lifetime.IsCompleted) + await controller.DisposeAsync().ConfigureAwait(false); + + validationStatus = controller.ApiValidationStatus; + + if (requireValidate && validationStatus == ApiValidationStatus.NeverValidated) + throw new JobException(ErrorCode.DreamMakerNeverValidated); + + logger.LogTrace("API validation status: {0}", validationStatus); + + job.DMApiVersion = controller.DMApiVersion; + } + + switch (validationStatus) + { + case ApiValidationStatus.RequiresUltrasafe: + job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; + return; + case ApiValidationStatus.RequiresSafe: + job.MinimumSecurityLevel = DreamDaemonSecurity.Safe; + return; + case ApiValidationStatus.RequiresTrusted: + job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted; + return; + case ApiValidationStatus.NeverValidated: + if (requireValidate) + throw new JobException(ErrorCode.DreamMakerNeverValidated); + job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; + break; + case ApiValidationStatus.BadValidationRequest: + case ApiValidationStatus.Incompatible: + throw new JobException(ErrorCode.DreamMakerInvalidValidation); + case ApiValidationStatus.UnaskedValidationRequest: + default: + throw new InvalidOperationException( + $"Session controller returned unexpected ApiValidationStatus: {validationStatus}"); + } + } + + /// + /// Compiles a .dme with DreamMaker. + /// + /// The path to the DreamMaker executable. + /// The for the operation. + /// The for the operation. + /// A representing the running operation. + async Task RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken) + { + using var dm = processExecutor.LaunchProcess( + dreamMakerPath, + ioManager.ResolvePath( + job.DirectoryName.ToString()), + $"-clean {job.DmeName}.{DmeExtension}", + true, + true, + true); + int exitCode; + using (cancellationToken.Register(() => dm.Terminate())) + exitCode = await dm.Lifetime.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + logger.LogDebug("DreamMaker exit code: {0}", exitCode); + job.Output = await dm.GetCombinedOutput(cancellationToken).ConfigureAwait(false); + currentDreamMakerOutput = job.Output; + logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output); + return exitCode; + } + + /// + /// Adds server side includes to the .dme being compiled. + /// + /// The for the operation. + /// The for the operation. + /// A representing the running operation. + async Task ModifyDme(Models.CompileJob job, CancellationToken cancellationToken) + { + var dmeFileName = String.Join('.', job.DmeName, DmeExtension); + var dmePath = ioManager.ConcatPath(job.DirectoryName.ToString(), dmeFileName); + var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken); + + var dmeModificationsTask = configuration.CopyDMFilesTo(dmeFileName, ioManager.ResolvePath(job.DirectoryName.ToString()), cancellationToken); + + var dmeBytes = await dmeReadTask.ConfigureAwait(false); + var dme = Encoding.UTF8.GetString(dmeBytes); + + var dmeModifications = await dmeModificationsTask.ConfigureAwait(false); + + if (dmeModifications == null || dmeModifications.TotalDmeOverwrite) + { + if (dmeModifications != null) + logger.LogDebug(".dme replacement configured!"); + else + logger.LogTrace("No .dme modifications required."); + return; + } + + var dmeLines = new List(dme.Split('\n', StringSplitOptions.None)); + for (var dmeLineIndex = 0; dmeLineIndex < dmeLines.Count; ++dmeLineIndex) + { + var line = dmeLines[dmeLineIndex]; + if (line.Contains("BEGIN_INCLUDE", StringComparison.Ordinal) && dmeModifications.HeadIncludeLine != null) + { + var headIncludeLineNumber = dmeLineIndex + 1; + logger.LogDebug( + "Inserting HeadInclude.dm at line {0}: {1}", + headIncludeLineNumber, + dmeModifications.HeadIncludeLine); + dmeLines.Insert(headIncludeLineNumber, dmeModifications.HeadIncludeLine); + ++dmeLineIndex; + } + else if (line.Contains("END_INCLUDE", StringComparison.Ordinal) && dmeModifications.TailIncludeLine != null) + { + logger.LogDebug( + "Inserting TailInclude.dm at line {0}: {1}", + dmeLineIndex, + dmeModifications.TailIncludeLine); + dmeLines.Insert(dmeLineIndex, dmeModifications.TailIncludeLine); + break; + } + } + + dmeBytes = Encoding.UTF8.GetBytes(String.Join(Environment.NewLine, dmeLines)); + await ioManager.WriteAllBytes(dmePath, dmeBytes, cancellationToken).ConfigureAwait(false); + } + + /// + /// Cleans up a failed compile . + /// + /// The running . + /// The associated with the . + /// The that was thrown. + /// A representing the running operation. + async Task CleanupFailedCompile(Models.CompileJob job, IRemoteDeploymentManager remoteDeploymentManager, Exception exception) + { + async Task CleanDir() + { + logger.LogTrace("Cleaning compile directory..."); + var jobPath = job.DirectoryName.ToString(); + try + { + // DCT: None available + await ioManager.DeleteDirectory(jobPath, default).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning(e, "Error cleaning up compile directory {0}!", ioManager.ResolvePath(jobPath)); + } + } + + // DCT: None available + await Task.WhenAll( + CleanDir(), + remoteDeploymentManager.FailDeployment( + job, + FormatExceptionForUsers(exception), + default)) + .ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs index fb2d80b88f..0eda36354f 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs @@ -1,20 +1,21 @@ using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// Sink for s + /// Sink for s. /// public interface ICompileJobSink : ILatestCompileJobProvider { /// - /// Load a new into the + /// Load a new into the . /// - /// The to load - /// The for the operation - /// A representing the running operation + /// The to load. + /// The for the operation. + /// A representing the running operation. Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs index 16e4cef3fb..4c4872a61c 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs @@ -1,47 +1,49 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// Factory for s + /// Factory for s. /// public interface IDmbFactory : ILatestCompileJobProvider, IHostedService, IDisposable { /// - /// Get a that completes when the result of a call to will be different than the previous call if any + /// Get a that completes when the result of a call to will be different than the previous call if any. /// - /// A representing the running operation + /// A representing the running operation. Task OnNewerDmb { get; } /// - /// If will succeed + /// If will succeed. /// bool DmbAvailable { get; } /// - /// Gets the next + /// Gets the next . /// - /// The amount of locks to give the resulting . It's must be called this many times to properly clean the job - /// A new + /// The amount of locks to give the resulting . It's must be called this many times to properly clean the job. + /// A new . IDmbProvider LockNextDmb(int lockCount); /// - /// Gets a for a given + /// Gets a for a given . /// - /// The to make the for - /// The for the operation - /// A resulting in a new representing the on success, on failure + /// The to make the for. + /// The for the operation. + /// A resulting in a new representing the on success, on failure. Task FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken); /// /// Deletes all compile jobs that are inactive in the Game folder. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task CleanUnusedCompileJobs(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs index da27442bc9..efc773a919 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs @@ -1,30 +1,31 @@ using System; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// Provides absolute paths to the latest compiled .dmbs + /// Provides absolute paths to the latest compiled .dmbs. /// public interface IDmbProvider : IDisposable { /// - /// The file name of the .dmb + /// The file name of the .dmb. /// string DmbName { get; } /// - /// The primary game directory with a trailing directory separator + /// The primary game directory with a trailing directory separator. /// string Directory { get; } /// - /// The of the .dmb + /// The of the .dmb. /// CompileJob CompileJob { get; } /// - /// Disposing the won't cause a cleanup of the working directory + /// Disposing the won't cause a cleanup of the working directory. /// void KeepAlive(); } diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs index a180138050..091a5ab750 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs @@ -1,13 +1,14 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// For managing the compiler + /// For managing the compiler. /// public interface IDreamMaker { @@ -25,4 +26,4 @@ namespace Tgstation.Server.Host.Components.Deployment Action progressReporter, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs index a9ea424e52..abc9ad8305 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Models; @@ -25,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote protected ILogger Logger { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -59,21 +61,21 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote var tasks = new List(); // added prs - var tmsAdded = deployedRevisionInformation + var addedTestMerges = deployedRevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => !previousRevisionInformation .ActiveTestMerges .Any(y => y.TestMerge.Number == x.Number)) .ToList(); - var tmsRemoved = previousRevisionInformation + var removedTestMerges = previousRevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => !deployedRevisionInformation .ActiveTestMerges .Any(y => y.TestMerge.Number == x.Number)) .ToList(); - var tmsUpdated = deployedRevisionInformation + var updatedTestMerges = deployedRevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => previousRevisionInformation @@ -81,15 +83,15 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote .Any(y => y.TestMerge.Number == x.Number)) .ToList(); - if (!tmsAdded.Any() && !tmsRemoved.Any() && !tmsUpdated.Any()) + if (!addedTestMerges.Any() && !removedTestMerges.Any() && !updatedTestMerges.Any()) return; Logger.LogTrace( "Commenting on {0} added, {1} removed, and {2} updated test merge sources...", - tmsAdded.Count, - tmsRemoved.Count, - tmsUpdated.Count); - foreach (var I in tmsAdded) + addedTestMerges.Count, + removedTestMerges.Count, + updatedTestMerges.Count); + foreach (var addedTestMerge in addedTestMerges) tasks.Add( CommentOnTestMergeSource( repositorySettings, @@ -98,24 +100,24 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote FormatTestMerge( repositorySettings, compileJob, - I, + addedTestMerge, repoOwner, repoName, false), - I.Number, + addedTestMerge.Number, cancellationToken)); - foreach (var I in tmsRemoved) + foreach (var removedTestMerge in removedTestMerges) tasks.Add( CommentOnTestMergeSource( repositorySettings, repoOwner, repoName, "#### Test Merge Removed", - I.Number, + removedTestMerge.Number, cancellationToken)); - foreach (var I in tmsUpdated) + foreach (var updatedTestMerge in updatedTestMerges) tasks.Add( CommentOnTestMergeSource( repositorySettings, @@ -124,53 +126,17 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote FormatTestMerge( repositorySettings, compileJob, - I, + updatedTestMerge, repoOwner, repoName, true), - I.Number, + updatedTestMerge.Number, cancellationToken)); if (tasks.Any()) await Task.WhenAll(tasks).ConfigureAwait(false); } - /// - /// Create a comment of a given 's source. - /// - /// The to use. - /// The . - /// The . - /// The comment to post. - /// The . - /// The for the operation. - /// A representing the running operation. - protected abstract Task CommentOnTestMergeSource( - RepositorySettings repositorySettings, - string remoteRepositoryOwner, - string remoteRepositoryName, - string comment, - int testMergeNumber, - CancellationToken cancellationToken); - - /// - /// Formats a comment for a given . - /// - /// The to use. - /// The test merge's . - /// The . - /// The . - /// The . - /// If is new, otherwise it has been updated to a different . - /// A representing the running operation. - protected abstract string FormatTestMerge( - RepositorySettings repositorySettings, - CompileJob compileJob, - TestMerge testMerge, - string remoteRepositoryOwner, - string remoteRepositoryName, - bool updated); - /// public abstract Task ApplyDeployment(CompileJob compileJob, CompileJob oldCompileJob, CancellationToken cancellationToken); @@ -195,5 +161,41 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote Api.Models.Internal.IGitRemoteInformation remoteInformation, CompileJob compileJob, CancellationToken cancellationToken); + + /// + /// Formats a comment for a given . + /// + /// The to use. + /// The test merge's . + /// The . + /// The . + /// The . + /// If is new, otherwise it has been updated to a different . + /// A representing the running operation. + protected abstract string FormatTestMerge( + RepositorySettings repositorySettings, + CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName, + bool updated); + + /// + /// Create a comment of a given 's source. + /// + /// The to use. + /// The . + /// The . + /// The comment to post. + /// The . + /// The for the operation. + /// A representing the running operation. + protected abstract Task CommentOnTestMergeSource( + RepositorySettings repositorySettings, + string remoteRepositoryOwner, + string remoteRepositoryName, + string comment, + int testMergeNumber, + CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs index 4ef0a40b6b..ee8c33fc14 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs @@ -1,13 +1,15 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Octokit; -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Octokit; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; @@ -17,7 +19,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment.Remote { /// - /// for GitHub.com + /// for GitHub.com. /// sealed class GitHubRemoteDeploymentManager : BaseRemoteDeploymentManager { @@ -32,7 +34,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote readonly IGitHubClientFactory gitHubClientFactory; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -103,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote Description = "TGS Game Deployment", Environment = $"TGS: {Metadata.Name}", ProductionEnvironment = true, - RequiredContexts = new Collection() + RequiredContexts = new Collection(), }) .WithToken(cancellationToken) .ConfigureAwait(false); @@ -122,7 +124,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote new NewDeploymentStatus(DeploymentState.InProgress) { Description = "The project is being deployed", - AutoInactive = false + AutoInactive = false, }) .WithToken(cancellationToken) .ConfigureAwait(false); @@ -145,60 +147,6 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote } } - async Task UpdateDeployment( - CompileJob compileJob, - string description, - DeploymentState deploymentState, - CancellationToken cancellationToken) - { - if (compileJob == null) - throw new ArgumentNullException(nameof(compileJob)); - - if (!compileJob.GitHubRepoId.HasValue || !compileJob.GitHubDeploymentId.HasValue) - { - Logger.LogTrace("Not updating deployment as it is missing a repo ID or deployment ID."); - return; - } - - Logger.LogTrace("Updating deployment {0} to {1}...", compileJob.GitHubDeploymentId.Value, deploymentState); - - string gitHubAccessToken = null; - await databaseContextFactory.UseContext( - async databaseContext => - gitHubAccessToken = await databaseContext - .RepositorySettings - .AsQueryable() - .Where(x => x.InstanceId == Metadata.Id) - .Select(x => x.AccessToken) - .FirstAsync(cancellationToken) - .ConfigureAwait(false)) - .ConfigureAwait(false); - - if (gitHubAccessToken == null) - { - Logger.LogWarning( - "GitHub access token disappeared during deployment, can't update to {0}!", - deploymentState); - return; - } - - var gitHubClient = gitHubClientFactory.CreateClient(gitHubAccessToken); - - await gitHubClient - .Repository - .Deployment - .Status - .Create( - compileJob.GitHubRepoId.Value, - compileJob.GitHubDeploymentId.Value, - new NewDeploymentStatus(deploymentState) - { - Description = description - }) - .WithToken(cancellationToken) - .ConfigureAwait(false); - } - /// public override Task StageDeployment( CompileJob compileJob, @@ -354,5 +302,67 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote compileJob.GitHubDeploymentId.HasValue ? $"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A%20{Metadata.Name})" : String.Empty); + + /// + /// Update the deployment for a given . + /// + /// The . + /// A description of the update. + /// The new . + /// The for the operation. + /// A representing the running operation. + async Task UpdateDeployment( + CompileJob compileJob, + string description, + DeploymentState deploymentState, + CancellationToken cancellationToken) + { + if (compileJob == null) + throw new ArgumentNullException(nameof(compileJob)); + + if (!compileJob.GitHubRepoId.HasValue || !compileJob.GitHubDeploymentId.HasValue) + { + Logger.LogTrace("Not updating deployment as it is missing a repo ID or deployment ID."); + return; + } + + Logger.LogTrace("Updating deployment {0} to {1}...", compileJob.GitHubDeploymentId.Value, deploymentState); + + string gitHubAccessToken = null; + await databaseContextFactory.UseContext( + async databaseContext => + gitHubAccessToken = await databaseContext + .RepositorySettings + .AsQueryable() + .Where(x => x.InstanceId == Metadata.Id) + .Select(x => x.AccessToken) + .FirstAsync(cancellationToken) + .ConfigureAwait(false)) + .ConfigureAwait(false); + + if (gitHubAccessToken == null) + { + Logger.LogWarning( + "GitHub access token disappeared during deployment, can't update to {0}!", + deploymentState); + return; + } + + var gitHubClient = gitHubClientFactory.CreateClient(gitHubAccessToken); + + await gitHubClient + .Repository + .Deployment + .Status + .Create( + compileJob.GitHubRepoId.Value, + compileJob.GitHubDeploymentId.Value, + new NewDeploymentStatus(deploymentState) + { + Description = description, + }) + .WithToken(cancellationToken) + .ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs index a81972fc87..7f19472593 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs @@ -1,13 +1,15 @@ -using GitLabApiClient; -using GitLabApiClient.Models.MergeRequests.Responses; -using GitLabApiClient.Models.Notes.Requests; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using GitLabApiClient; +using GitLabApiClient.Models.MergeRequests.Responses; +using GitLabApiClient.Models.Notes.Requests; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; @@ -15,12 +17,12 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment.Remote { /// - /// for GitLab.com + /// for GitLab.com. /// sealed class GitLabRemoteDeploymentManager : BaseRemoteDeploymentManager { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -29,59 +31,6 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote { } - /// - protected override Task CommentOnTestMergeSource( - RepositorySettings repositorySettings, - string remoteRepositoryOwner, - string remoteRepositoryName, - string comment, - int testMergeNumber, - CancellationToken cancellationToken) - { - var client = repositorySettings.AccessToken != null - ? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken) - : new GitLabClient(GitLabRemoteFeatures.GitLabUrl); - - return client - .MergeRequests - .CreateNoteAsync( - $"{remoteRepositoryOwner}/{remoteRepositoryName}", - testMergeNumber, - new CreateMergeRequestNoteRequest(comment)) - .WithToken(cancellationToken); - } - - /// - protected override string FormatTestMerge( - RepositorySettings repositorySettings, - CompileJob compileJob, - TestMerge testMerge, - string remoteRepositoryOwner, - string remoteRepositoryName, - bool updated) => String.Format( - CultureInfo.InvariantCulture, - "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}", - Environment.NewLine, - repositorySettings.ShowTestMergeCommitters.Value - ? String.Format( - CultureInfo.InvariantCulture, - "{0}{0}##### Merged By{0}{1}", - Environment.NewLine, - testMerge.MergedBy.Name) - : String.Empty, - testMerge.TargetCommitSha, - testMerge.Comment != null - ? String.Format( - CultureInfo.InvariantCulture, - "{0}{0}##### Comment{0}{1}", - Environment.NewLine, - testMerge.Comment) - : String.Empty, - updated ? "Updated" : "Deployed", - Metadata.Name, - compileJob.RevisionInformation.OriginCommitSha, - compileJob.RevisionInformation.CommitSha); - /// public override async Task> RemoveMergedTestMerges( IRepository repository, @@ -152,7 +101,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote /// public override Task ApplyDeployment( CompileJob compileJob, - CompileJob oldCompileJob, CancellationToken cancellationToken) => Task.CompletedTask; + CompileJob oldCompileJob, + CancellationToken cancellationToken) => Task.CompletedTask; /// public override Task FailDeployment( @@ -171,5 +121,58 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote Api.Models.Internal.IGitRemoteInformation remoteInformation, CompileJob compileJob, CancellationToken cancellationToken) => Task.CompletedTask; + + /// + protected override Task CommentOnTestMergeSource( + RepositorySettings repositorySettings, + string remoteRepositoryOwner, + string remoteRepositoryName, + string comment, + int testMergeNumber, + CancellationToken cancellationToken) + { + var client = repositorySettings.AccessToken != null + ? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken) + : new GitLabClient(GitLabRemoteFeatures.GitLabUrl); + + return client + .MergeRequests + .CreateNoteAsync( + $"{remoteRepositoryOwner}/{remoteRepositoryName}", + testMergeNumber, + new CreateMergeRequestNoteRequest(comment)) + .WithToken(cancellationToken); + } + + /// + protected override string FormatTestMerge( + RepositorySettings repositorySettings, + CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName, + bool updated) => String.Format( + CultureInfo.InvariantCulture, + "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}", + Environment.NewLine, + repositorySettings.ShowTestMergeCommitters.Value + ? String.Format( + CultureInfo.InvariantCulture, + "{0}{0}##### Merged By{0}{1}", + Environment.NewLine, + testMerge.MergedBy.Name) + : String.Empty, + testMerge.TargetCommitSha, + testMerge.Comment != null + ? String.Format( + CultureInfo.InvariantCulture, + "{0}{0}##### Comment{0}{1}", + Environment.NewLine, + testMerge.Comment) + : String.Empty, + updated ? "Updated" : "Deployed", + Metadata.Name, + compileJob.RevisionInformation.OriginCommitSha, + compileJob.RevisionInformation.CommitSha); } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs index 26947bb659..faed399d66 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Models; diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManagerFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManagerFactory.cs index daa6c5a95c..690b39d2ce 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManagerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManagerFactory.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Components.Deployment.Remote { diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs index 4c84dc4a6f..3a19f96471 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Models; diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs index 6bc60ab1fa..fbd3f658ab 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs @@ -1,5 +1,7 @@ +using System; + using Microsoft.Extensions.Logging; -using System; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Core; @@ -36,7 +38,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs index 2c9c589f8e..c01c30f2cc 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; @@ -41,7 +42,7 @@ namespace Tgstation.Server.Host.Components.Deployment readonly ISymlinkFactory symlinkFactory; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs index 7ee060ab39..0bf982cfa3 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs @@ -1,10 +1,11 @@ using System; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { /// - /// Temporary + /// Temporary . /// sealed class TemporaryDmbProvider : IDmbProvider { @@ -18,11 +19,11 @@ namespace Tgstation.Server.Host.Components.Deployment public CompileJob CompileJob { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . public TemporaryDmbProvider(string directory, string dmb, CompileJob compileJob) { DmbName = dmb ?? throw new ArgumentNullException(nameof(dmb)); @@ -31,9 +32,11 @@ namespace Tgstation.Server.Host.Components.Deployment } /// - public void Dispose() { } + public void Dispose() + { + } /// public void KeepAlive() => throw new NotSupportedException(); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs index 9df9ce74df..f5129d7a06 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; @@ -11,19 +12,19 @@ namespace Tgstation.Server.Host.Components.Events sealed class EventConsumer : IEventConsumer { /// - /// The for the + /// The for the . /// readonly IConfiguration configuration; /// - /// The for the + /// The for the . /// IWatchdog watchdog; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public EventConsumer(IConfiguration configuration) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -43,9 +44,9 @@ namespace Tgstation.Server.Host.Components.Events } /// - /// Set the for the + /// Set the for the . /// - /// The value of + /// The value of . public void SetWatchdog(IWatchdog watchdog) { #pragma warning disable IDE0016 // Use 'throw' expression diff --git a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs index c42a4b2a6b..c1b14e7b67 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Events public string ScriptName { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public EventScriptAttribute(string scriptName) diff --git a/src/Tgstation.Server.Host/Components/Events/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs index a91bc2ab3f..b7ca4a24c4 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -1,7 +1,7 @@ -namespace Tgstation.Server.Host.Components.Events +namespace Tgstation.Server.Host.Components.Events { /// - /// Types of events. Mirror in tgs.dm + /// Types of events. Mirror in tgs.dm. /// public enum EventType { diff --git a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs index 8d2cc4c03d..5b5acea272 100644 --- a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index b7e64db9b2..1d26b9382d 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,5 +1,6 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; + +using Microsoft.Extensions.Hosting; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/IInstanceCore.cs b/src/Tgstation.Server.Host/Components/IInstanceCore.cs index 27552e5bc6..101ca82e01 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceCore.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceCore.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -9,17 +10,17 @@ using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components { /// - /// For interacting with the instance services + /// For interacting with the instance services. /// public interface IInstanceCore : ILatestCompileJobProvider, IRenameNotifyee { /// - /// The for the + /// The for the . /// IRepositoryManager RepositoryManager { get; } /// - /// The for the + /// The for the . /// IByondManager ByondManager { get; } @@ -29,25 +30,25 @@ namespace Tgstation.Server.Host.Components IDreamMaker DreamMaker { get; } /// - /// The for the + /// The for the . /// IWatchdog Watchdog { get; } /// - /// The for the + /// The for the . /// IChatManager Chat { get; } /// - /// The for the + /// The for the . /// IConfiguration Configuration { get; } /// - /// Change the for the + /// Change the for the . /// - /// The new auto update inteval - /// A representing the running operation + /// The new auto update inteval. + /// A representing the running operation. Task SetAutoUpdateInterval(uint newInterval); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs index 494aaf130f..fb50f512a8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Components { /// - /// Provider for s + /// Provider for s. /// interface IInstanceCoreProvider { diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 473837ccf2..8f3c7d2ab4 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -1,20 +1,22 @@ -using Microsoft.Extensions.Hosting; -using System.Threading.Tasks; +using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Host.Components.Interop.Bridge; namespace Tgstation.Server.Host.Components { /// - /// Factory for creating s + /// Factory for creating s. /// interface IInstanceFactory : IHostedService { /// - /// Create an + /// Create an . /// /// The to use. - /// The + /// The . /// A resulting in a new . Task CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index d4922d783c..fa573312e1 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,12 +1,13 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// - /// For managing s + /// For managing s. /// public interface IInstanceManager : IBridgeDispatcher { @@ -16,36 +17,36 @@ namespace Tgstation.Server.Host.Components Task Ready { get; } /// - /// Get the associated with given + /// Get the associated with given . /// - /// The of the desired + /// The of the desired . /// The associated with the given if it is online, otherwise. IInstanceReference GetInstanceReference(Api.Models.Instance metadata); /// - /// Online an + /// Online an . /// - /// The of the desired - /// The for the operation - /// A representing the running operation + /// The of the desired . + /// The for the operation. + /// A representing the running operation. Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken); /// - /// Offline an + /// Offline an . /// - /// The of the desired - /// The performing the operation - /// The for the operation - /// A representing the running operation + /// The of the desired . + /// The performing the operation. + /// The for the operation. + /// A representing the running operation. Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken); /// - /// Move an + /// Move an . /// /// The of the desired with the updated path. /// The old path of the . will have this set on if the operation fails. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task MoveInstance(Models.Instance metadata, string oldPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs index c6de7b32f1..03844a7510 100644 --- a/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs +++ b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index c5e13de2ae..603602c5f8 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,11 +1,13 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Serilog.Context; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Serilog.Context; + using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; @@ -21,7 +23,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify sealed class Instance : IInstance { /// @@ -48,17 +50,17 @@ namespace Tgstation.Server.Host.Components public IDreamMaker DreamMaker { get; } /// - /// The for the + /// The for the . /// readonly IDmbFactory dmbFactory; /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// The for the + /// The for the . /// readonly IEventConsumer eventConsumer; @@ -68,12 +70,12 @@ namespace Tgstation.Server.Host.Components readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly Api.Models.Instance metadata; @@ -83,30 +85,30 @@ namespace Tgstation.Server.Host.Components readonly object timerLock; /// - /// The auto update + /// The auto update . /// Task timerTask; /// - /// for + /// for . /// CancellationTokenSource timerCts; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . - /// The value of + /// The value of . public Instance( Api.Models.Instance metadata, IRepositoryManager repositoryManager, @@ -152,313 +154,6 @@ namespace Tgstation.Server.Host.Components } } - /// - /// The for updating the repository. - /// - /// The for the . - /// The for the . - /// The being run. - /// The progress reporter action for the . - /// The for the operation. - /// A representing the running operation. -#pragma warning disable CA1502 // Cyclomatic complexity - Task RepositoryAutoUpdateJob( - IInstanceCore core, - IDatabaseContextFactory databaseContextFactory, - Job job, - Action progressReporter, - CancellationToken cancellationToken) - => databaseContextFactory.UseContext( - async databaseContext => - { - if (core != this) - throw new InvalidOperationException(DifferentCoreExceptionMessage); - - // assume 5 steps with synchronize - const int ProgressSections = 7; - const int ProgressStep = 100 / ProgressSections; - - var repositorySettingsTask = databaseContext - .RepositorySettings - .AsQueryable() - .Where(x => x.InstanceId == metadata.Id) - .FirstAsync(cancellationToken); - - const int NumSteps = 3; - var doneSteps = 0; - - Action NextProgressReporter() - { - var tmpDoneSteps = doneSteps; - ++doneSteps; - return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps); - } - - using var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false); - if (repo == null) - { - logger.LogTrace("Aborting repo update, no repository!"); - return; - } - - var startSha = repo.Head; - if (!repo.Tracking) - { - logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!"); - return; - } - - var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - - // the main point of auto update is to pull the remote - await repo.FetchOrigin( - repositorySettings.AccessUser, - repositorySettings.AccessToken, - NextProgressReporter(), - cancellationToken) - .ConfigureAwait(false); - - RevisionInformation currentRevInfo = null; - - Task LoadRevInfo() => databaseContext.RevisionInformations - .AsQueryable() - .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id) - .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) - .FirstOrDefaultAsync(cancellationToken); - - var hasDbChanges = false; - - // take appropriate auto update actions - var shouldSyncTracked = false; - var currentRevInfoTask = LoadRevInfo(); - - var result = await repo.MergeOrigin( - repositorySettings.CommitterName, - repositorySettings.CommitterEmail, - NextProgressReporter(), - cancellationToken) - .ConfigureAwait(false); - - async Task UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable updatedTestMerges) - { - if (currentRevInfo == null) - currentRevInfo = await LoadRevInfo().ConfigureAwait(false); - - if (currentRevInfo == default) - { - logger.LogInformation(Repository.Repository.OriginTrackingErrorTemplate, currentHead); - onOrigin = true; - } - - var attachedInstance = new Models.Instance - { - Id = metadata.Id - }; - var oldRevInfo = currentRevInfo; - currentRevInfo = new RevisionInformation - { - CommitSha = currentHead, - Timestamp = await repo.TimestampCommit(currentHead, cancellationToken).ConfigureAwait(false), - OriginCommitSha = onOrigin - ? currentHead - : await repo.GetOriginSha(cancellationToken).ConfigureAwait(false), - Instance = attachedInstance - }; - if (!onOrigin) - currentRevInfo.ActiveTestMerges = new List( - updatedTestMerges ?? oldRevInfo.ActiveTestMerges); - - databaseContext.Instances.Attach(attachedInstance); - databaseContext.RevisionInformations.Add(currentRevInfo); - hasDbChanges = true; - } - - var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value; - var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager( - metadata, - repo.RemoteGitProvider.Value); - if (result.HasValue) - { - currentRevInfo = await currentRevInfoTask.ConfigureAwait(false); - - var updatedTestMerges = await remoteDeploymentManager.RemoveMergedTestMerges( - repo, - repositorySettings, - currentRevInfo, - cancellationToken) - .ConfigureAwait(false); - - if (updatedTestMerges.Count == 0) - { - logger.LogTrace("All test merges have been merged on remote"); - preserveTestMerges = false; - } - else - { - var lastRevInfoWasOriginCommit = - currentRevInfo == default - || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha; - var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit; - - var currentHead = repo.Head; - if (currentHead != startSha) - { - await UpdateRevInfo(currentHead, stillOnOrigin, updatedTestMerges).ConfigureAwait(false); - shouldSyncTracked = stillOnOrigin; - } - } - } - else if (preserveTestMerges) - throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict); - - if (!preserveTestMerges) - { - logger.LogTrace("Resetting to origin..."); - await repo.ResetToOrigin(NextProgressReporter(), cancellationToken).ConfigureAwait(false); - - var currentHead = repo.Head; - - currentRevInfo = await databaseContext.RevisionInformations - .AsQueryable() - .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) - .FirstOrDefaultAsync(cancellationToken) - .ConfigureAwait(false); - - if (currentHead != startSha && currentRevInfo == default) - await UpdateRevInfo(currentHead, true, null).ConfigureAwait(false); - - shouldSyncTracked = true; - } - - // synch if necessary - if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits.Value)) - { - var pushedOrigin = await repo.Sychronize( - repositorySettings.AccessUser, - repositorySettings.AccessToken, - repositorySettings.CommitterName, - repositorySettings.CommitterEmail, - NextProgressReporter(), - shouldSyncTracked, - cancellationToken).ConfigureAwait(false); - var currentHead = repo.Head; - if (currentHead != currentRevInfo.CommitSha) - await UpdateRevInfo(currentHead, pushedOrigin, null).ConfigureAwait(false); - } - - if (hasDbChanges) - try - { - await databaseContext.Save(cancellationToken).ConfigureAwait(false); - } - catch - { - // DCT: Cancellation token is for job, operation must run regardless - await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false); - throw; - } - - progressReporter(5 * ProgressStep); - }); -#pragma warning restore CA1502 // Cyclomatic complexity - - /// - /// Pull the repository and compile for every set of given - /// - /// How many minutes the operation should repeat. Does not include running time - /// The for the operation - /// A representing the running operation -#pragma warning disable CA1502 // TODO: Decomplexify - async Task TimerLoop(uint minutes, CancellationToken cancellationToken) - { - logger.LogDebug("Entering auto-update loop"); - while (true) - try - { - await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : minutes), cancellationToken).ConfigureAwait(false); - logger.LogInformation("Beginning auto update..."); - await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); - try - { - var repositoryUpdateJob = new Job - { - Instance = new Models.Instance - { - Id = metadata.Id - }, - Description = "Scheduled repository update", - CancelRightsType = RightsType.Repository, - CancelRight = (ulong)RepositoryRights.CancelPendingChanges - }; - - await jobManager.RegisterOperation( - repositoryUpdateJob, - RepositoryAutoUpdateJob, - cancellationToken) - .ConfigureAwait(false); - - // DCT: First token will cancel the job, second is for cancelling the cancellation, unwanted - await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, default).ConfigureAwait(false); - - Job compileProcessJob; - using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) - { - var deploySha = repo.Head; - if (deploySha == null) - { - logger.LogTrace("Aborting auto update, repository error!"); - continue; - } - - if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha) - { - logger.LogTrace("Aborting auto update, same revision as latest CompileJob"); - continue; - } - - // finally set up the job - compileProcessJob = new Job - { - Instance = repositoryUpdateJob.Instance, - Description = "Scheduled code deployment", - CancelRightsType = RightsType.DreamMaker, - CancelRight = (ulong)DreamMakerRights.CancelCompile - }; - - await jobManager.RegisterOperation( - compileProcessJob, - (core, databaseContextFactory, job, progressReporter, jobCancellationToken) => - { - if (core != this) - throw new InvalidOperationException(DifferentCoreExceptionMessage); - return DreamMaker.DeploymentProcess( - job, - databaseContextFactory, - progressReporter, - jobCancellationToken); - }, - cancellationToken) - .ConfigureAwait(false); - } - - await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - logger.LogWarning(e, "Error in auto update loop!"); - continue; - } - } - catch (OperationCanceledException) - { - logger.LogDebug("Cancelled auto update loop!"); - break; - } - - logger.LogTrace("Leaving auto update loop..."); - } -#pragma warning restore CA1502 - /// public Task InstanceRenamed(string newName, CancellationToken cancellationToken) { @@ -546,5 +241,312 @@ namespace Tgstation.Server.Host.Components /// public CompileJob LatestCompileJob() => dmbFactory.LatestCompileJob(); + + /// + /// The for updating the repository. + /// + /// The for the . + /// The for the . + /// The being run. + /// The progress reporter action for the . + /// The for the operation. + /// A representing the running operation. +#pragma warning disable CA1502 // Cyclomatic complexity + Task RepositoryAutoUpdateJob( + IInstanceCore core, + IDatabaseContextFactory databaseContextFactory, + Job job, + Action progressReporter, + CancellationToken cancellationToken) + => databaseContextFactory.UseContext( + async databaseContext => + { + if (core != this) + throw new InvalidOperationException(DifferentCoreExceptionMessage); + + // assume 5 steps with synchronize + const int ProgressSections = 7; + const int ProgressStep = 100 / ProgressSections; + + var repositorySettingsTask = databaseContext + .RepositorySettings + .AsQueryable() + .Where(x => x.InstanceId == metadata.Id) + .FirstAsync(cancellationToken); + + const int NumSteps = 3; + var doneSteps = 0; + + Action NextProgressReporter() + { + var tmpDoneSteps = doneSteps; + ++doneSteps; + return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps); + } + + using var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false); + if (repo == null) + { + logger.LogTrace("Aborting repo update, no repository!"); + return; + } + + var startSha = repo.Head; + if (!repo.Tracking) + { + logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!"); + return; + } + + var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); + + // the main point of auto update is to pull the remote + await repo.FetchOrigin( + repositorySettings.AccessUser, + repositorySettings.AccessToken, + NextProgressReporter(), + cancellationToken) + .ConfigureAwait(false); + + RevisionInformation currentRevInfo = null; + + Task LoadRevInfo() => databaseContext.RevisionInformations + .AsQueryable() + .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id) + .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) + .FirstOrDefaultAsync(cancellationToken); + + var hasDbChanges = false; + + // take appropriate auto update actions + var shouldSyncTracked = false; + var currentRevInfoTask = LoadRevInfo(); + + var result = await repo.MergeOrigin( + repositorySettings.CommitterName, + repositorySettings.CommitterEmail, + NextProgressReporter(), + cancellationToken) + .ConfigureAwait(false); + + async Task UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable updatedTestMerges) + { + if (currentRevInfo == null) + currentRevInfo = await LoadRevInfo().ConfigureAwait(false); + + if (currentRevInfo == default) + { + logger.LogInformation(Repository.Repository.OriginTrackingErrorTemplate, currentHead); + onOrigin = true; + } + + var attachedInstance = new Models.Instance + { + Id = metadata.Id, + }; + var oldRevInfo = currentRevInfo; + currentRevInfo = new RevisionInformation + { + CommitSha = currentHead, + Timestamp = await repo.TimestampCommit(currentHead, cancellationToken).ConfigureAwait(false), + OriginCommitSha = onOrigin + ? currentHead + : await repo.GetOriginSha(cancellationToken).ConfigureAwait(false), + Instance = attachedInstance, + }; + if (!onOrigin) + currentRevInfo.ActiveTestMerges = new List( + updatedTestMerges ?? oldRevInfo.ActiveTestMerges); + + databaseContext.Instances.Attach(attachedInstance); + databaseContext.RevisionInformations.Add(currentRevInfo); + hasDbChanges = true; + } + + var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value; + var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager( + metadata, + repo.RemoteGitProvider.Value); + if (result.HasValue) + { + currentRevInfo = await currentRevInfoTask.ConfigureAwait(false); + + var updatedTestMerges = await remoteDeploymentManager.RemoveMergedTestMerges( + repo, + repositorySettings, + currentRevInfo, + cancellationToken) + .ConfigureAwait(false); + + if (updatedTestMerges.Count == 0) + { + logger.LogTrace("All test merges have been merged on remote"); + preserveTestMerges = false; + } + else + { + var lastRevInfoWasOriginCommit = + currentRevInfo == default + || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha; + var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit; + + var currentHead = repo.Head; + if (currentHead != startSha) + { + await UpdateRevInfo(currentHead, stillOnOrigin, updatedTestMerges).ConfigureAwait(false); + shouldSyncTracked = stillOnOrigin; + } + } + } + else if (preserveTestMerges) + throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict); + + if (!preserveTestMerges) + { + logger.LogTrace("Resetting to origin..."); + await repo.ResetToOrigin(NextProgressReporter(), cancellationToken).ConfigureAwait(false); + + var currentHead = repo.Head; + + currentRevInfo = await databaseContext.RevisionInformations + .AsQueryable() + .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) + .FirstOrDefaultAsync(cancellationToken) + .ConfigureAwait(false); + + if (currentHead != startSha && currentRevInfo == default) + await UpdateRevInfo(currentHead, true, null).ConfigureAwait(false); + + shouldSyncTracked = true; + } + + // synch if necessary + if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits.Value)) + { + var pushedOrigin = await repo.Sychronize( + repositorySettings.AccessUser, + repositorySettings.AccessToken, + repositorySettings.CommitterName, + repositorySettings.CommitterEmail, + NextProgressReporter(), + shouldSyncTracked, + cancellationToken).ConfigureAwait(false); + var currentHead = repo.Head; + if (currentHead != currentRevInfo.CommitSha) + await UpdateRevInfo(currentHead, pushedOrigin, null).ConfigureAwait(false); + } + + if (hasDbChanges) + try + { + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch + { + // DCT: Cancellation token is for job, operation must run regardless + await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false); + throw; + } + + progressReporter(5 * ProgressStep); + }); +#pragma warning restore CA1502 // Cyclomatic complexity + + /// + /// Pull the repository and compile for every set of given . + /// + /// How many minutes the operation should repeat. Does not include running time. + /// The for the operation. + /// A representing the running operation. +#pragma warning disable CA1502 // TODO: Decomplexify + async Task TimerLoop(uint minutes, CancellationToken cancellationToken) + { + logger.LogDebug("Entering auto-update loop"); + while (true) + try + { + await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : minutes), cancellationToken).ConfigureAwait(false); + logger.LogInformation("Beginning auto update..."); + await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); + try + { + var repositoryUpdateJob = new Job + { + Instance = new Models.Instance + { + Id = metadata.Id, + }, + Description = "Scheduled repository update", + CancelRightsType = RightsType.Repository, + CancelRight = (ulong)RepositoryRights.CancelPendingChanges, + }; + + await jobManager.RegisterOperation( + repositoryUpdateJob, + RepositoryAutoUpdateJob, + cancellationToken) + .ConfigureAwait(false); + + // DCT: First token will cancel the job, second is for cancelling the cancellation, unwanted + await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, default).ConfigureAwait(false); + + Job compileProcessJob; + using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + var deploySha = repo.Head; + if (deploySha == null) + { + logger.LogTrace("Aborting auto update, repository error!"); + continue; + } + + if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha) + { + logger.LogTrace("Aborting auto update, same revision as latest CompileJob"); + continue; + } + + // finally set up the job + compileProcessJob = new Job + { + Instance = repositoryUpdateJob.Instance, + Description = "Scheduled code deployment", + CancelRightsType = RightsType.DreamMaker, + CancelRight = (ulong)DreamMakerRights.CancelCompile, + }; + + await jobManager.RegisterOperation( + compileProcessJob, + (core, databaseContextFactory, job, progressReporter, jobCancellationToken) => + { + if (core != this) + throw new InvalidOperationException(DifferentCoreExceptionMessage); + return DreamMaker.DeploymentProcess( + job, + databaseContextFactory, + progressReporter, + jobCancellationToken); + }, + cancellationToken) + .ConfigureAwait(false); + } + + await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (!(e is OperationCanceledException)) + { + logger.LogWarning(e, "Error in auto update loop!"); + continue; + } + } + catch (OperationCanceledException) + { + logger.LogDebug("Cancelled auto update loop!"); + break; + } + + logger.LogTrace("Leaving auto update loop..."); + } +#pragma warning restore CA1502 } } diff --git a/src/Tgstation.Server.Host/Components/InstanceContainer.cs b/src/Tgstation.Server.Host/Components/InstanceContainer.cs index 3a1e64f92a..9cb320cce5 100644 --- a/src/Tgstation.Server.Host/Components/InstanceContainer.cs +++ b/src/Tgstation.Server.Host/Components/InstanceContainer.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { /// - /// Wrapper for managing s + /// Wrapper for managing s. /// sealed class InstanceContainer { @@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Components ulong referenceCount; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public InstanceContainer(IInstance instance) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 45b14b674a..32f05929c7 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; @@ -28,82 +30,82 @@ namespace Tgstation.Server.Host.Components sealed class InstanceFactory : IInstanceFactory { /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the . /// readonly ITopicClientFactory topicClientFactory; /// - /// The for the + /// The for the . /// readonly ICryptographySuite cryptographySuite; /// - /// The for the + /// The for the . /// readonly ISynchronousIOManager synchronousIOManager; /// - /// The for the + /// The for the . /// readonly ISymlinkFactory symlinkFactory; /// - /// The for the + /// The for the . /// readonly IByondInstaller byondInstaller; /// - /// The for the + /// The for the . /// readonly IChatManagerFactory chatFactory; /// - /// The for the + /// The for the . /// readonly IProcessExecutor processExecutor; /// - /// The for the + /// The for the . /// readonly IPostWriteHandler postWriteHandler; /// - /// The for the + /// The for the . /// readonly IWatchdogFactory watchdogFactory; /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// The for the + /// The for the . /// readonly INetworkPromptReaper networkPromptReaper; /// - /// The for the + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; @@ -143,24 +145,24 @@ namespace Tgstation.Server.Host.Components readonly GeneralConfiguration generalConfiguration; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index dd2455d3c9..80aa3b45cd 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -1,12 +1,14 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; @@ -35,47 +37,47 @@ namespace Tgstation.Server.Host.Components public Task Ready => readyTcs.Task; /// - /// The for the + /// The for the . /// readonly IInstanceFactory instanceFactory; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// The for the + /// The for the . /// readonly IServerControl serverControl; /// - /// The for the + /// The for the . /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// The for the + /// The for the . /// readonly IServerPortProvider serverPortProvider; @@ -85,7 +87,7 @@ namespace Tgstation.Server.Host.Components readonly ISwarmService swarmService; /// - /// The for the + /// The for the . /// readonly ILogger logger; @@ -120,26 +122,26 @@ namespace Tgstation.Server.Host.Components readonly TaskCompletionSource readyTcs; /// - /// If the has been 'd + /// If the has been 'd. /// bool disposed; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . /// The containing the value of . /// The containing the value of . - /// The value of + /// The value of . public InstanceManager( IInstanceFactory instanceFactory, IIOManager ioManager, @@ -185,8 +187,8 @@ namespace Tgstation.Server.Host.Components disposed = true; } - foreach (var I in instances) - await I.Value.Instance.DisposeAsync().ConfigureAwait(false); + foreach (var instanceKvp in instances) + await instanceKvp.Value.Instance.DisposeAsync().ConfigureAwait(false); instanceStateChangeSemaphore.Dispose(); @@ -239,7 +241,7 @@ namespace Tgstation.Server.Host.Components { var targetInstance = new Models.Instance { - Id = instance.Id + Id = instance.Id, }; db.Instances.Attach(targetInstance); targetInstance.Path = oldPath; @@ -312,9 +314,10 @@ namespace Tgstation.Server.Host.Components .Where(x => x.Instance.Id == metadata.Id) .Select(x => new Models.Job { - Id = x.Id + Id = x.Id, }); - await jobs.ForEachAsync(job => + await jobs.ForEachAsync( + job => { lock (tasks) tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken)); @@ -391,7 +394,6 @@ namespace Tgstation.Server.Host.Components await InitializeSwarm(cancellationToken).ConfigureAwait(false); - List dbInstances = null; var instanceEnumeration = databaseContextFactory.UseContext( async databaseContext => dbInstances = await databaseContext @@ -469,22 +471,6 @@ namespace Tgstation.Server.Host.Components } } - /// - /// Check we have a valid system identity. - /// - private void CheckSystemCompatibility() - { - using (var systemIdentity = systemIdentityFactory.GetCurrent()) - { - if (!systemIdentity.CanCreateSymlinks) - throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!"); - } - - // This runs before the real socket is opened, ensures we don't perform reattaches unless we're fairly certain the bind won't fail - // If it does fail, DD will be killed. - SocketExtensions.BindTest(serverPortProvider.HttpApiPort, true); - } - /// public async Task ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken) { @@ -548,6 +534,22 @@ namespace Tgstation.Server.Host.Components } } + /// + /// Check we have a valid system identity. + /// + private void CheckSystemCompatibility() + { + using (var systemIdentity = systemIdentityFactory.GetCurrent()) + { + if (!systemIdentity.CanCreateSymlinks) + throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!"); + } + + // This runs before the real socket is opened, ensures we don't perform reattaches unless we're fairly certain the bind won't fail + // If it does fail, DD will be killed. + SocketExtensions.BindTest(serverPortProvider.HttpApiPort, true); + } + /// /// Initializes the connection to the TGS swarm. /// diff --git a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs index 0830098235..d2359c1b3b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs +++ b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -35,7 +36,7 @@ namespace Tgstation.Server.Host.Components IInstanceCore actualInstance; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs index c823e82aa3..2de0e824b8 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs @@ -33,6 +33,6 @@ /// /// DreamDaemon requesting a be sent. /// - ChatSend + ChatSend, } } diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs index ac6b470d90..f174ffec1f 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Chat.Commands; diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs index 99c94047cc..ade7d5adfe 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs @@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge Action onDispose; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public BridgeRegistration(Action onDispose) @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge /// public void Dispose() { - lock(lockObject) + lock (lockObject) { onDispose?.Invoke(); onDispose = null; diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs index 218830576f..2a7a9e00d7 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs @@ -16,4 +16,4 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge /// A resulting in the for the request or if the request could not be dispatched. Task ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs index 5d262a277e..53ed19e80a 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs @@ -8,4 +8,4 @@ /// DMApiParameters DMApiParameters { get; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeRegistration.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeRegistration.cs index 12c6611e82..189846f081 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeRegistration.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeRegistration.cs @@ -8,4 +8,4 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge interface IBridgeRegistration : IDisposable { } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs index ffa2f09022..686fd2fcf0 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -19,7 +20,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge public Version ServerVersion { get; } /// - /// The port the HTTP server is running on + /// The port the HTTP server is running on. /// public ushort ServerPort { get; } @@ -29,17 +30,17 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge public bool ApiValidateOnly { get; } /// - /// The of the owner at the time of launch + /// The of the owner at the time of launch. /// public string InstanceName { get; set; } /// - /// The of the launch + /// The of the launch. /// public Api.Models.Internal.RevisionInformation Revision { get; } /// - /// The level of the launch + /// The level of the launch. /// public DreamDaemonSecurity? SecurityLevel { get; } @@ -49,7 +50,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge public IReadOnlyCollection TestMerges { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The to use. /// The to get revision information from. @@ -77,7 +78,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge { CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, Timestamp = dmbProvider.CompileJob.RevisionInformation.Timestamp, - OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha + OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha, }; TestMerges = (IReadOnlyCollection)dmbProvider diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs index 4ec7088ba2..ee8a96f7c7 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs @@ -1,16 +1,17 @@ -using System; +using System; using System.Globalization; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Interop.Bridge { /// - /// This model mirrors /datum/tgs_revision_information/test_merge + /// This model mirrors /datum/tgs_revision_information/test_merge. /// public sealed class TestMergeInformation : TestMergeModelBase { /// - /// The unix time of when the test merge was applied + /// The unix time of when the test merge was applied. /// public string TimeMerged { get; set; } @@ -27,15 +28,15 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge } /// - /// The of the + /// The of the . /// public RevisionInformation Revision { get; set; } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The to build from - /// The value of + /// The to build from. + /// The value of . public TestMergeInformation(Models.TestMerge testMerge, RevisionInformation revision) : base(testMerge) { TimeMerged = testMerge?.MergedAt.Ticks.ToString(CultureInfo.InvariantCulture) ?? throw new ArgumentNullException(nameof(testMerge)); diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs b/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs index 28211682bd..745d2c4d4f 100644 --- a/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs +++ b/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs @@ -17,4 +17,4 @@ namespace Tgstation.Server.Host.Components.Interop /// public ICollection ChannelIds { get; set; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatUpdate.cs b/src/Tgstation.Server.Host/Components/Interop/ChatUpdate.cs index 06ca6b6604..12e9b408ab 100644 --- a/src/Tgstation.Server.Host/Components/Interop/ChatUpdate.cs +++ b/src/Tgstation.Server.Host/Components/Interop/ChatUpdate.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Tgstation.Server.Host.Components.Chat; namespace Tgstation.Server.Host.Components.Interop @@ -16,7 +17,7 @@ namespace Tgstation.Server.Host.Components.Interop public IEnumerable Channels { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The that forms the value of . public ChatUpdate(IEnumerable channels) diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs index b4697d8952..37c12cc642 100644 --- a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs +++ b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs @@ -1,13 +1,15 @@ +using System; + using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System; + using Tgstation.Server.Host.Extensions.Converters; using Tgstation.Server.Host.Properties; namespace Tgstation.Server.Host.Components.Interop { /// - /// Constants used for communication with the DMAPI + /// Constants used for communication with the DMAPI. /// static class DMApiConstants { @@ -43,15 +45,15 @@ namespace Tgstation.Server.Host.Components.Interop { ContractResolver = new DefaultContractResolver { - NamingStrategy = new CamelCaseNamingStrategy() + NamingStrategy = new CamelCaseNamingStrategy(), }, Converters = new JsonConverter[] { new VersionConverter(), - new BoolConverter() + new BoolConverter(), }, DefaultValueHandling = DefaultValueHandling.Ignore, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, }; } } diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs index 22cfc28983..a6faad3292 100644 --- a/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs +++ b/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Interop public abstract class DMApiParameters { /// - /// Used to identify and authenticate the DreamDaemon instance + /// Used to identify and authenticate the DreamDaemon instance. /// [Required] public string AccessIdentifier { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/ChatCommand.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/ChatCommand.cs index 63e42f04f9..5f50d6bba8 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/ChatCommand.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/ChatCommand.cs @@ -1,30 +1,31 @@ using System; + using Tgstation.Server.Host.Components.Chat; namespace Tgstation.Server.Host.Components.Interop.Topic { /// - /// Represents a chat command to be handled by DD + /// Represents a chat command to be handled by DD. /// sealed class ChatCommand { /// - /// The command name + /// The command name. /// public string Name { get; } /// - /// The command params + /// The command params. /// public string Params { get; } /// - /// The that sent the command + /// The that sent the command. /// public ChatUser User { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs index 9ab63c7ffc..54a8d496a5 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; + using Tgstation.Server.Host.Components.Events; namespace Tgstation.Server.Host.Components.Interop.Topic @@ -22,14 +23,14 @@ namespace Tgstation.Server.Host.Components.Interop.Topic public IReadOnlyCollection Parameters { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The that forms the value of . public EventNotification(EventType eventType, IEnumerable parameters = null) { Type = eventType; - Parameters = parameters?.ToList() ?? throw new ArgumentNullException(nameof(parameters)); ; + Parameters = parameters?.ToList() ?? throw new ArgumentNullException(nameof(parameters)); } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs index ca8a5e603e..83a71feaf3 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs @@ -51,6 +51,6 @@ namespace Tgstation.Server.Host.Components.Interop.Topic /// /// Notify the server of a reattach and potentially new version. /// - ServerRestarted + ServerRestarted, } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs index 4752434fb2..1deaac3848 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Host.Components.Session; namespace Tgstation.Server.Host.Components.Interop.Topic @@ -50,16 +51,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic public Version NewServerVersion { get; } /// - /// Initializes a new instance of the . - /// - /// The value of . - private TopicParameters(TopicCommandType commandType) - { - CommandType = commandType; - } - - /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(ChatCommand chatCommand) @@ -69,7 +61,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(EventNotification eventNotification) @@ -79,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(ushort newPort) @@ -89,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(RebootState newRebootState) @@ -99,7 +91,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(string newInstanceName) @@ -109,7 +101,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicParameters(ChatUpdate channelsUpdate) @@ -119,7 +111,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// TGS's new API port. @@ -131,12 +123,21 @@ namespace Tgstation.Server.Host.Components.Interop.Topic } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// Constructor for s. public TopicParameters() : this(TopicCommandType.Heartbeat) { } + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + TopicParameters(TopicCommandType commandType) + { + CommandType = commandType; + } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs index 2a689698f7..340c546f7d 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; + using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Interop.Topic diff --git a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs index 3bdc876709..e379ae233a 100644 --- a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Components.Repository diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs index 9a068eb8c9..c6395b65f4 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using Octokit; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Octokit; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; @@ -36,7 +38,7 @@ namespace Tgstation.Server.Host.Components.Repository readonly IGitHubClientFactory gitHubClientFactory; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The for the . @@ -110,7 +112,7 @@ namespace Tgstation.Server.Host.Components.Repository Comment = parameters.Comment, Number = parameters.Number, TargetCommitSha = revisionToUse, - Url = pr?.HtmlUrl ?? errorMessage + Url = pr?.HtmlUrl ?? errorMessage, }; return testMerge; diff --git a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs index b20c139553..ab008aadbd 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs @@ -1,8 +1,10 @@ -using GitLabApiClient; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using GitLabApiClient; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Extensions; @@ -36,7 +38,7 @@ namespace Tgstation.Server.Host.Components.Repository public override string RemoteRepositoryName { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The remote repository . @@ -80,7 +82,7 @@ namespace Tgstation.Server.Host.Components.Repository Comment = parameters.Comment, Number = parameters.Number, TargetCommitSha = mr.Sha, - Url = mr.WebUrl + Url = mr.WebUrl, }; } catch (Exception ex) @@ -95,7 +97,7 @@ namespace Tgstation.Server.Host.Components.Repository Comment = parameters.Comment, Number = parameters.Number, TargetCommitSha = parameters.TargetCommitSha, - Url = ex.Message + Url = ex.Message, }; } } diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs index 92835dd7ad..edcfabbf7c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; @@ -39,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Repository readonly Dictionary cachedLookups; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The remote repository . @@ -81,7 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - /// Implementation of + /// Implementation of . /// /// The . /// The . diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs index 2f864b258e..9a9762b6a4 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs @@ -1,5 +1,7 @@ +using System; + using Microsoft.Extensions.Logging; -using System; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; @@ -24,7 +26,7 @@ namespace Tgstation.Server.Host.Components.Repository readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs index 496a015187..3568f0747e 100644 --- a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs +++ b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs @@ -3,16 +3,16 @@ namespace Tgstation.Server.Host.Components.Repository { /// - /// For generating s + /// For generating s. /// interface ICredentialsProvider { /// - /// Generate a from a given and + /// Generate a from a given and . /// - /// The optional username to use in the - /// The optional password to use in the - /// A new + /// The optional username to use in the . + /// The optional password to use in the . + /// A new . CredentialsHandler GenerateCredentialsHandler(string username, string password); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs index 7b4de75b0e..4d993bb918 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs @@ -1,5 +1,6 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs index d91dca020d..0971fdc74d 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs @@ -1,7 +1,7 @@ -namespace Tgstation.Server.Host.Components.Repository +namespace Tgstation.Server.Host.Components.Repository { /// - /// Provides features for remote git services + /// Provides features for remote git services. /// interface IGitRemoteFeatures : IGitRemoteAdditionalInformation { @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Repository string TestMergeRefSpecFormatter { get; } /// - /// Get + /// Get. /// string TestMergeLocalBranchNameFormatter { get; } } diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs index b2c00aac6d..42a55e6285 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Components.Repository diff --git a/src/Tgstation.Server.Host/Components/Repository/ILibGit2Commands.cs b/src/Tgstation.Server.Host/Components/Repository/ILibGit2Commands.cs index c125fafdd6..6c0ab0da49 100644 --- a/src/Tgstation.Server.Host/Components/Repository/ILibGit2Commands.cs +++ b/src/Tgstation.Server.Host/Components/Repository/ILibGit2Commands.cs @@ -1,5 +1,6 @@ -using LibGit2Sharp; -using System.Collections.Generic; +using System.Collections.Generic; + +using LibGit2Sharp; namespace Tgstation.Server.Host.Components.Repository { diff --git a/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs index 047671181d..b03048ccf6 100644 --- a/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs @@ -1,8 +1,9 @@ -using LibGit2Sharp; -using System; +using System; using System.Threading; using System.Threading.Tasks; +using LibGit2Sharp; + namespace Tgstation.Server.Host.Components.Repository { /// diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index e1c0bd0af1..f23e2c8d0c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -1,121 +1,122 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Components.Repository { /// - /// Represents an on-disk git repository + /// Represents an on-disk git repository. /// public interface IRepository : IGitRemoteAdditionalInformation, IDisposable { /// - /// If tracks an upstream branch + /// If tracks an upstream branch. /// bool Tracking { get; } /// - /// The SHA of the HEAD + /// The SHA of the HEAD. /// string Head { get; } /// - /// The current reference the HEAD is using. This can be a branch or tag + /// The current reference the HEAD is using. This can be a branch or tag. /// string Reference { get; } /// - /// The current origin remote the is using + /// The current origin remote the is using. /// Uri Origin { get; } /// - /// Checks if a given is a sha + /// Checks if a given is a sha. /// - /// The git object to check - /// The for the operation - /// A resulting in if is a sha, otherwise + /// The git object to check. + /// The for the operation. + /// A resulting in if is a sha, otherwise. Task IsSha(string committish, CancellationToken cancellationToken); /// - /// Checks out a given + /// Checks out a given . /// - /// The sha or reference to checkout - /// to report 0-100 progress of the operation - /// The for the operation - /// A representing the running operation + /// The sha or reference to checkout. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A representing the running operation. Task CheckoutObject(string committish, Action progressReporter, CancellationToken cancellationToken); /// - /// Attempt to merge the revision specified by a given set of into HEAD + /// Attempt to merge the revision specified by a given set of into HEAD. /// - /// The of the pull request - /// The name of the merge committer - /// The e-mail of the merge committer - /// The username to fetch from the origin repository - /// The password to fetch from the origin repository - /// to report 0-100 progress of the operation - /// The for the operation - /// A resulting in a representing the merge result that is after a fast forward or up to date, on a non-fast-forward, on a conflict + /// The of the pull request. + /// The name of the merge committer. + /// The e-mail of the merge committer. + /// The username to fetch from the origin repository. + /// The password to fetch from the origin repository. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a non-fast-forward, on a conflict. Task AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action progressReporter, CancellationToken cancellationToken); /// - /// Fetch commits from the origin repository + /// Fetch commits from the origin repository. /// - /// The username to fetch from the origin repository - /// The password to fetch from the origin repository - /// to report 0-100 progress of the operation - /// The for the operation - /// A representing the running operation + /// The username to fetch from the origin repository. + /// The password to fetch from the origin repository. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A representing the running operation. Task FetchOrigin(string username, string password, Action progressReporter, CancellationToken cancellationToken); /// - /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository + /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository. /// - /// to report 0-100 progress of the operation - /// The for the operation - /// A resulting in the SHA of the new HEAD + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A resulting in the SHA of the new HEAD. Task ResetToOrigin(Action progressReporter, CancellationToken cancellationToken); /// - /// Requires the current HEAD to be a reference. Hard resets the reference to the given sha + /// Requires the current HEAD to be a reference. Hard resets the reference to the given sha. /// - /// The sha hash to reset to - /// to report 0-100 progress of the operation - /// The for the operation - /// A resulting in the SHA of the new HEAD + /// The sha hash to reset to. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A resulting in the SHA of the new HEAD. Task ResetToSha(string sha, Action progressReporter, CancellationToken cancellationToken); /// - /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository + /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository. /// - /// The name of the merge committer - /// The e-mail of the merge committer - /// to report 0-100 progress of the operation - /// The for the operation - /// A resulting in a representing the merge result that is after a fast forward, on a merge or up to date, on a conflict + /// The name of the merge committer. + /// The e-mail of the merge committer. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A resulting in a representing the merge result that is after a fast forward, on a merge or up to date, on a conflict. Task MergeOrigin(string committerName, string committerEmail, Action progressReporter, CancellationToken cancellationToken); /// - /// Runs the synchronize event script and attempts to push any changes made to the if on a tracked branch + /// Runs the synchronize event script and attempts to push any changes made to the if on a tracked branch. /// - /// The username to fetch from the origin repository - /// The password to fetch from the origin repository - /// The name of the potential committer - /// The e-mail of the potential committer - /// to report 0-100 progress of the operation - /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch - /// The for the operation - /// A resulting in if commits were pushed to the tracked origin reference, otherwise + /// The username to fetch from the origin repository. + /// The password to fetch from the origin repository. + /// The name of the potential committer. + /// The e-mail of the potential committer. + /// to report 0-100 progress of the operation. + /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch. + /// The for the operation. + /// A resulting in if commits were pushed to the tracked origin reference, otherwise. Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); /// - /// Copies the current working directory to a given + /// Copies the current working directory to a given . /// - /// The path to copy repository contents to - /// The for the operation - /// A representing the running operation + /// The path to copy repository contents to. + /// The for the operation. + /// A representing the running operation. Task CopyTo(string path, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 06bf3fb467..9c239ea773 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -5,12 +5,12 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Repository { /// - /// Factory for creating and loading s + /// Factory for creating and loading s. /// public interface IRepositoryManager : IDisposable { /// - /// If something is holding a lock on the repository + /// If something is holding a lock on the repository. /// bool InUse { get; } @@ -20,23 +20,23 @@ namespace Tgstation.Server.Host.Components.Repository bool CloneInProgress { get; } /// - /// Attempt to load the from the default location + /// Attempt to load the from the default location. /// - /// The for the operation - /// The loaded if it exists, otherwise + /// The for the operation. + /// The loaded if it exists, otherwise. Task LoadRepository(CancellationToken cancellationToken); /// - /// Clone the repository at + /// Clone the repository at . /// - /// The of the remote repository to clone - /// The branch to clone - /// The username to clone from - /// The password to clone from - /// A function to report 0-100 progress of the clone + /// The of the remote repository to clone. + /// The branch to clone. + /// The username to clone from . + /// The password to clone from . + /// A function to report 0-100 progress of the clone. /// If submodules should be recusively cloned and initialized. - /// The for the operation - /// The newly cloned , if one already exists + /// The for the operation. + /// The newly cloned , if one already exists. Task CloneRepository( Uri url, string initialBranch, @@ -47,10 +47,10 @@ namespace Tgstation.Server.Host.Components.Repository CancellationToken cancellationToken); /// - /// Delete the current repository + /// Delete the current repository. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task DeleteRepository(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2Commands.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2Commands.cs index 58158078ab..4d557149bd 100644 --- a/src/Tgstation.Server.Host/Components/Repository/LibGit2Commands.cs +++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2Commands.cs @@ -1,7 +1,8 @@ -using LibGit2Sharp; -using System; +using System; using System.Collections.Generic; +using LibGit2Sharp; + namespace Tgstation.Server.Host.Components.Repository { /// diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs index 182ca3c3dc..1dda89d84d 100644 --- a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs @@ -1,9 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + using LibGit2Sharp; using LibGit2Sharp.Handlers; using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; @@ -19,7 +21,7 @@ namespace Tgstation.Server.Host.Components.Repository readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public LibGit2RepositoryFactory(ILogger logger) @@ -31,8 +33,8 @@ namespace Tgstation.Server.Host.Components.Repository public void CreateInMemory() { logger.LogTrace("Creating in-memory libgit2 repository..."); - using var _ = new LibGit2Sharp.Repository(); - logger.LogTrace("Success"); + using (new LibGit2Sharp.Repository()) + logger.LogTrace("Success"); } /// @@ -56,19 +58,23 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - try + public Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - logger.LogTrace("Cloning {0} into {1}...", url, path); - LibGit2Sharp.Repository.Clone(url.ToString(), path, cloneOptions); - } - catch (UserCancelledException ex) - { - logger.LogTrace(ex, "Suppressing clone cancellation exception"); - cancellationToken.ThrowIfCancellationRequested(); - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + try + { + logger.LogTrace("Cloning {0} into {1}...", url, path); + LibGit2Sharp.Repository.Clone(url.ToString(), path, cloneOptions); + } + catch (UserCancelledException ex) + { + logger.LogTrace(ex, "Suppressing clone cancellation exception"); + cancellationToken.ThrowIfCancellationRequested(); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) => @@ -82,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Repository return new UsernamePasswordCredentials { Username = username, - Password = password + Password = password, }; if (supportsAnonymous) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index fe593874c6..934c34d2e3 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -1,12 +1,14 @@ -using LibGit2Sharp; -using LibGit2Sharp.Handlers; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using LibGit2Sharp; +using LibGit2Sharp.Handlers; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Events; @@ -29,17 +31,17 @@ namespace Tgstation.Server.Host.Components.Repository public const string DefaultCommitterEmail = "tgstation-server@users.noreply.github.com"; /// - /// Template error message for when tracking of the most recent origin commit fails + /// Template error message for when tracking of the most recent origin commit fails. /// 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."; /// - /// The branch name used for publishing testmerge commits + /// The branch name used for publishing testmerge commits. /// public const string RemoteTemporaryBranchName = "___TGSTempBranch"; /// - /// Used when a reference cannot be determined + /// Used when a reference cannot be determined. /// const string UnknownReference = ""; @@ -65,7 +67,7 @@ namespace Tgstation.Server.Host.Components.Repository public Uri Origin => new Uri(libGitRepo.Network.Remotes.First().Url); /// - /// The for the + /// The for the . /// readonly LibGit2Sharp.IRepository libGitRepo; @@ -75,17 +77,17 @@ namespace Tgstation.Server.Host.Components.Repository readonly ILibGit2Commands commands; /// - /// The for the + /// The for the . /// readonly IIOManager ioMananger; /// - /// The for the + /// The for the . /// readonly IEventConsumer eventConsumer; /// - /// The for the + /// The for the . /// readonly ICredentialsProvider credentialsProvider; @@ -95,12 +97,12 @@ namespace Tgstation.Server.Host.Components.Repository readonly IGitRemoteFeatures gitRemoteFeatures; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// to be taken when is called + /// to be taken when is called. /// readonly Action onDispose; @@ -110,10 +112,10 @@ namespace Tgstation.Server.Host.Components.Repository bool disposed; /// - /// Converts a given to a + /// Converts a given to a . /// - /// to report 0-100 progress of the operation - /// A based on + /// to report 0-100 progress of the operation. + /// A based on . static CheckoutProgressHandler CheckoutProgressHandler(Action progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100)); /// @@ -127,16 +129,16 @@ namespace Tgstation.Server.Host.Components.Repository } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . /// The value of . - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . /// The to provide the value of . - /// The value of - /// The value if + /// The value of . + /// The value if . public Repository( LibGit2Sharp.IRepository libGitRepo, ILibGit2Commands commands, @@ -177,84 +179,8 @@ namespace Tgstation.Server.Host.Components.Repository onDispose(); } - /// - /// Generate a standard set of - /// - /// to report 0-100 progress of the operation - /// The username for the - /// The password for the - /// The for the operation - /// A new set of - PushOptions GeneratePushOptions(Action progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions - { - OnPackBuilderProgress = (stage, current, total) => - { - var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25; - progressReporter(baseProgress + ((int)(25 * ((float)current) / total))); - return !cancellationToken.IsCancellationRequested; - }, - OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, - OnPushTransferProgress = (a, sentBytes, totalBytes) => - { - progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes))); - return !cancellationToken.IsCancellationRequested; - }, - CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password) - }; - - /// - /// Runs a blocking force checkout to - /// - /// The committish to checkout - /// Progress reporter - /// The for the operation - void RawCheckout(string committish, Action progressReporter, CancellationToken cancellationToken) - { - logger.LogTrace("Checkout: {0}", committish); - - progressReporter(0); - cancellationToken.ThrowIfCancellationRequested(); - - var checkoutOptions = new CheckoutOptions - { - CheckoutModifiers = CheckoutModifiers.Force, - OnCheckoutProgress = CheckoutProgressHandler(progressReporter) - }; - - void RunCheckout() => commands.Checkout( - libGitRepo, - checkoutOptions, - committish); - - try - { - RunCheckout(); - } - catch (NotFoundException) - { - // Maybe (likely) a remote? - var remoteName = $"origin/{committish}"; - var potentialBranch = libGitRepo.Branches.FirstOrDefault( - branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal)); - cancellationToken.ThrowIfCancellationRequested(); - - if (potentialBranch == default) - throw; - - logger.LogDebug("Creating local branch for {0}...", potentialBranch.FriendlyName); - libGitRepo.CreateBranch(committish, potentialBranch.Tip); - cancellationToken.ThrowIfCancellationRequested(); - - RunCheckout(); - } - - cancellationToken.ThrowIfCancellationRequested(); - - libGitRepo.RemoveUntrackedFiles(); - } - /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public async Task AddTestMerge( TestMergeParameters testMergeParameters, string committerName, @@ -273,7 +199,8 @@ namespace Tgstation.Server.Host.Components.Repository if (progressReporter == null) throw new ArgumentNullException(nameof(progressReporter)); - logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>", + logger.LogDebug( + "Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>", testMergeParameters.Number, testMergeParameters.TargetCommitSha?.Substring(0, 7), testMergeParameters.Comment, @@ -304,79 +231,86 @@ namespace Tgstation.Server.Host.Components.Repository MergeResult result = null; var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.UtcNow); - await Task.Factory.StartNew(() => - { - try + await Task.Factory.StartNew( + () => { try { - logger.LogTrace("Fetching refspec {0}...", refSpec); + try + { + logger.LogTrace("Fetching refspec {0}...", refSpec); - var remote = libGitRepo.Network.Remotes.First(); - progressReporter(0); - commands.Fetch( - libGitRepo, - refSpecList, - remote, - new FetchOptions - { - Prune = true, - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => + var remote = libGitRepo.Network.Remotes.First(); + progressReporter(0); + commands.Fetch( + libGitRepo, + refSpecList, + remote, + new FetchOptions { - var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); - progressReporter((int)percentage); - return !cancellationToken.IsCancellationRequested; + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => + { + var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter((int)percentage); + return !cancellationToken.IsCancellationRequested; + }, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, + CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password), }, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password) - }, - logMessage); + logMessage); + } + catch (UserCancelledException) + { + } + catch (LibGit2SharpException ex) + { + CheckBadCredentialsException(ex); + } + + cancellationToken.ThrowIfCancellationRequested(); + + libGitRepo.RemoveUntrackedFiles(); + + cancellationToken.ThrowIfCancellationRequested(); + + testMergeParameters.TargetCommitSha = libGitRepo.Lookup(testMergeParameters.TargetCommitSha ?? localBranchName).Sha; + + cancellationToken.ThrowIfCancellationRequested(); + + logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha.Substring(0, 7), Reference); + + result = libGitRepo.Merge(testMergeParameters.TargetCommitSha, sig, new MergeOptions + { + CommitOnSuccess = commitMessage == null, + FailOnConflict = true, + FastForwardStrategy = FastForwardStrategy.NoFastForward, + SkipReuc = true, + OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50))), + }); } - catch (UserCancelledException) { } - catch (LibGit2SharpException ex) + finally { - CheckBadCredentialsException(ex); + libGitRepo.Branches.Remove(localBranchName); } cancellationToken.ThrowIfCancellationRequested(); + if (result.Status == MergeStatus.Conflicts) + { + var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha; + logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo); + RawCheckout(revertTo, progressReporter, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + } + libGitRepo.RemoveUntrackedFiles(); - - cancellationToken.ThrowIfCancellationRequested(); - - testMergeParameters.TargetCommitSha = libGitRepo.Lookup(testMergeParameters.TargetCommitSha ?? localBranchName).Sha; - - cancellationToken.ThrowIfCancellationRequested(); - - logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha.Substring(0, 7), Reference); - - result = libGitRepo.Merge(testMergeParameters.TargetCommitSha, sig, new MergeOptions - { - CommitOnSuccess = commitMessage == null, - FailOnConflict = true, - FastForwardStrategy = FastForwardStrategy.NoFastForward, - SkipReuc = true, - OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50))) - }); - } - finally - { - libGitRepo.Branches.Remove(localBranchName); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (result.Status == MergeStatus.Conflicts) - { - var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha; - logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo); - RawCheckout(revertTo, progressReporter, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - } - - libGitRepo.RemoveUntrackedFiles(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); if (result.Status == MergeStatus.Conflicts) { @@ -387,7 +321,7 @@ namespace Tgstation.Server.Host.Components.Repository originalCommit.Tip.Sha, testMergeParameters.TargetCommitSha, originalCommit.FriendlyName ?? UnknownReference, - testMergeBranchName + testMergeBranchName, }, cancellationToken) .ConfigureAwait(false); @@ -397,10 +331,15 @@ namespace Tgstation.Server.Host.Components.Repository if (commitMessage != null && result.Status != MergeStatus.UpToDate) { logger.LogTrace("Committing merge: \"{0}\"...", commitMessage); - await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions - { - PrettifyMessage = true - }), cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew( + () => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions + { + PrettifyMessage = true, + }), + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); } await eventConsumer.HandleEvent( @@ -409,14 +348,14 @@ namespace Tgstation.Server.Host.Components.Repository { testMergeParameters.Number.ToString(CultureInfo.InvariantCulture), testMergeParameters.TargetCommitSha, - testMergeParameters.Comment + testMergeParameters.Comment, }, cancellationToken) .ConfigureAwait(false); return result.Status != MergeStatus.NonFastForward; } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// public async Task CheckoutObject(string committish, Action progressReporter, CancellationToken cancellationToken) @@ -427,11 +366,16 @@ namespace Tgstation.Server.Host.Components.Repository throw new ArgumentNullException(nameof(progressReporter)); logger.LogDebug("Checkout object: {0}...", committish); await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, cancellationToken).ConfigureAwait(false); - await Task.Factory.StartNew(() => - { - libGitRepo.RemoveUntrackedFiles(); - RawCheckout(committish, progressReporter, cancellationToken); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew( + () => + { + libGitRepo.RemoveUntrackedFiles(); + RawCheckout(committish, progressReporter, cancellationToken); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); } /// @@ -441,81 +385,48 @@ namespace Tgstation.Server.Host.Components.Repository throw new ArgumentNullException(nameof(progressReporter)); logger.LogDebug("Fetch origin..."); await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); - await Task.Factory.StartNew(() => - { - var remote = libGitRepo.Network.Remotes.First(); - try + await Task.Factory.StartNew( + () => { - commands.Fetch( - libGitRepo, - remote - .FetchRefSpecs - .Select(x => x.Specification), - remote, - new FetchOptions - { - Prune = true, - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => + var remote = libGitRepo.Network.Remotes.First(); + try + { + commands.Fetch( + libGitRepo, + remote + .FetchRefSpecs + .Select(x => x.Specification), + remote, + new FetchOptions { - var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); - progressReporter((int)percentage); - return !cancellationToken.IsCancellationRequested; + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => + { + var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter((int)percentage); + return !cancellationToken.IsCancellationRequested; + }, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, + CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password), }, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password) - }, - "Fetch origin commits"); - } - catch (UserCancelledException) - { - cancellationToken.ThrowIfCancellationRequested(); - } - catch (LibGit2SharpException ex) - { - CheckBadCredentialsException(ex); - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + "Fetch origin commits"); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + catch (LibGit2SharpException ex) + { + CheckBadCredentialsException(ex); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); } - /// - /// Force push the current repository HEAD to ; - /// - /// The username to fetch from the origin repository - /// The password to fetch from the origin repository - /// to report 0-100 progress of the operation - /// The for the operation - /// A representing the running operation - Task PushHeadToTemporaryBranch(string username, string password, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - logger.LogInformation("Pushing changes to temporary remote branch..."); - var branch = libGitRepo.CreateBranch(RemoteTemporaryBranchName); - try - { - cancellationToken.ThrowIfCancellationRequested(); - var remote = libGitRepo.Network.Remotes.First(); - try - { - var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName); - libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken)); - var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName); - libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken)); - } - catch (UserCancelledException) - { - cancellationToken.ThrowIfCancellationRequested(); - } - catch (LibGit2SharpException e) - { - logger.LogWarning(e, "Unable to push to temporary branch!"); - } - } - finally - { - libGitRepo.Branches.Remove(branch); - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); - /// public async Task ResetToOrigin(Action progressReporter, CancellationToken cancellationToken) { @@ -530,29 +441,33 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task ResetToSha(string sha, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (sha == null) - throw new ArgumentNullException(nameof(sha)); - if (progressReporter == null) - throw new ArgumentNullException(nameof(progressReporter)); - - logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7)); - - libGitRepo.RemoveUntrackedFiles(); - cancellationToken.ThrowIfCancellationRequested(); - - var gitObject = libGitRepo.Lookup(sha, ObjectType.Commit); - cancellationToken.ThrowIfCancellationRequested(); - - if (gitObject == null) - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha)); - - libGitRepo.Reset(ResetMode.Hard, gitObject.Peel(), new CheckoutOptions + public Task ResetToSha(string sha, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - OnCheckoutProgress = CheckoutProgressHandler(progressReporter) - }); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + if (sha == null) + throw new ArgumentNullException(nameof(sha)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + + logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7)); + + libGitRepo.RemoveUntrackedFiles(); + cancellationToken.ThrowIfCancellationRequested(); + + var gitObject = libGitRepo.Lookup(sha, ObjectType.Commit); + cancellationToken.ThrowIfCancellationRequested(); + + if (gitObject == null) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha)); + + libGitRepo.Reset(ResetMode.Hard, gitObject.Peel(), new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progressReporter), + }); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// public async Task CopyTo(string path, CancellationToken cancellationToken) @@ -564,15 +479,19 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task GetOriginSha(CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (!Tracking) - throw new JobException(ErrorCode.RepoReferenceRequired); + public Task GetOriginSha(CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (!Tracking) + throw new JobException(ErrorCode.RepoReferenceRequired); - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - return libGitRepo.Head.TrackedBranch.Tip.Sha; - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + return libGitRepo.Head.TrackedBranch.Tip.Sha; + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// public async Task MergeOrigin(string committerName, string committerEmail, Action progressReporter, CancellationToken cancellationToken) @@ -586,44 +505,49 @@ namespace Tgstation.Server.Host.Components.Repository var oldHead = libGitRepo.Head; var oldTip = oldHead.Tip; - await Task.Factory.StartNew(() => - { - if (!Tracking) - throw new JobException(ErrorCode.RepoReferenceRequired); - - libGitRepo.RemoveUntrackedFiles(); - - cancellationToken.ThrowIfCancellationRequested(); - - trackedBranch = libGitRepo.Head.TrackedBranch; - logger.LogDebug( - "Merge origin/{0}: <{1} ({2})>", - trackedBranch.FriendlyName, - committerName, - committerEmail); - result = libGitRepo.Merge(trackedBranch, new Signature(committerName, committerEmail, DateTimeOffset.UtcNow), new MergeOptions + await Task.Factory.StartNew( + () => { - CommitOnSuccess = true, - FailOnConflict = true, - FastForwardStrategy = FastForwardStrategy.Default, - SkipReuc = true, - OnCheckoutProgress = CheckoutProgressHandler(progressReporter) - }); + if (!Tracking) + throw new JobException(ErrorCode.RepoReferenceRequired); - cancellationToken.ThrowIfCancellationRequested(); + libGitRepo.RemoveUntrackedFiles(); - if (result.Status == MergeStatus.Conflicts) - { - logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName); - libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions - { - OnCheckoutProgress = CheckoutProgressHandler(progressReporter) - }); cancellationToken.ThrowIfCancellationRequested(); - } - libGitRepo.RemoveUntrackedFiles(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + trackedBranch = libGitRepo.Head.TrackedBranch; + logger.LogDebug( + "Merge origin/{0}: <{1} ({2})>", + trackedBranch.FriendlyName, + committerName, + committerEmail); + result = libGitRepo.Merge(trackedBranch, new Signature(committerName, committerEmail, DateTimeOffset.UtcNow), new MergeOptions + { + CommitOnSuccess = true, + FailOnConflict = true, + FastForwardStrategy = FastForwardStrategy.Default, + SkipReuc = true, + OnCheckoutProgress = CheckoutProgressHandler(progressReporter), + }); + + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Status == MergeStatus.Conflicts) + { + logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName); + libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progressReporter), + }); + cancellationToken.ThrowIfCancellationRequested(); + } + + libGitRepo.RemoveUntrackedFiles(); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); if (result.Status == MergeStatus.Conflicts) { @@ -667,12 +591,16 @@ namespace Tgstation.Server.Host.Components.Repository var startHead = Head; logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail); - await Task.Factory.StartNew(() => - { - libGitRepo.Config.Set("user.name", committerName); - cancellationToken.ThrowIfCancellationRequested(); - libGitRepo.Config.Set("user.email", committerEmail); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew( + () => + { + libGitRepo.Config.Set("user.name", committerName); + cancellationToken.ThrowIfCancellationRequested(); + libGitRepo.Config.Set("user.email", committerEmail); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); try @@ -681,7 +609,7 @@ namespace Tgstation.Server.Host.Components.Repository EventType.RepoPreSynchronize, new List { - ioMananger.ResolvePath() + ioMananger.ResolvePath(), }, cancellationToken) .ConfigureAwait(false); @@ -689,15 +617,20 @@ namespace Tgstation.Server.Host.Components.Repository finally { logger.LogTrace("Resetting and cleaning untracked files..."); - await Task.Factory.StartNew(() => - { - libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions + await Task.Factory.StartNew( + () => { - OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)) - }); - cancellationToken.ThrowIfCancellationRequested(); - libGitRepo.RemoveUntrackedFiles(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)), + }); + cancellationToken.ThrowIfCancellationRequested(); + libGitRepo.RemoveUntrackedFiles(); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); } void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90)); @@ -717,89 +650,102 @@ namespace Tgstation.Server.Host.Components.Repository logger.LogInformation("Synchronizing with origin..."); - return await Task.Factory.StartNew(() => - { - var remote = libGitRepo.Network.Remotes.First(); - try + return await Task.Factory.StartNew( + () => { - libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken)); - return true; - } - catch (NonFastForwardException) - { - logger.LogInformation("Synchronize aborted, non-fast forward!"); - return false; - } - catch (UserCancelledException e) - { - cancellationToken.ThrowIfCancellationRequested(); - throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e); - } - catch (LibGit2SharpException e) - { - logger.LogWarning(e, "Unable to make synchronization push!"); - return false; - } - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); + var remote = libGitRepo.Network.Remotes.First(); + try + { + libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken)); + return true; + } + catch (NonFastForwardException) + { + logger.LogInformation("Synchronize aborted, non-fast forward!"); + return false; + } + catch (UserCancelledException e) + { + cancellationToken.ThrowIfCancellationRequested(); + throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e); + } + catch (LibGit2SharpException e) + { + logger.LogWarning(e, "Unable to make synchronization push!"); + return false; + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current) + .ConfigureAwait(false); } /// - public Task IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - // check if it's a tag - var gitObject = libGitRepo.Lookup(committish, ObjectType.Tag); - if (gitObject != null) - return false; - cancellationToken.ThrowIfCancellationRequested(); + public Task IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + // check if it's a tag + var gitObject = libGitRepo.Lookup(committish, ObjectType.Tag); + if (gitObject != null) + return false; + cancellationToken.ThrowIfCancellationRequested(); - // check if it's a branch - if (libGitRepo.Branches[committish] != null) - return false; - cancellationToken.ThrowIfCancellationRequested(); + // check if it's a branch + if (libGitRepo.Branches[committish] != null) + return false; + cancellationToken.ThrowIfCancellationRequested(); - // err on the side of references, if we can't look it up, assume its a reference - if (libGitRepo.Lookup(committish) != null) - return true; - return false; - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + // err on the side of references, if we can't look it up, assume its a reference + if (libGitRepo.Lookup(committish) != null) + return true; + return false; + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// - public Task ShaIsParent(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - var targetCommit = libGitRepo.Lookup(sha); - if(targetCommit == null) + public Task ShaIsParent(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - logger.LogTrace("Commit {0} not found in repository", sha); + var targetCommit = libGitRepo.Lookup(sha); + if (targetCommit == null) + { + logger.LogTrace("Commit {0} not found in repository", sha); + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + var startSha = Head; + var mergeResult = libGitRepo.Merge( + targetCommit, + new Signature( + DefaultCommitterName, + DefaultCommitterEmail, + DateTimeOffset.UtcNow), + new MergeOptions + { + FastForwardStrategy = FastForwardStrategy.FastForwardOnly, + FailOnConflict = true, + }); + + if (mergeResult.Status == MergeStatus.UpToDate) + return true; + + commands.Checkout( + libGitRepo, + new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force, + }, + startSha); + return false; - } - - cancellationToken.ThrowIfCancellationRequested(); - var startSha = Head; - var mergeResult = libGitRepo.Merge( - targetCommit, - new Signature( - DefaultCommitterName, - DefaultCommitterEmail, - DateTimeOffset.UtcNow), - new MergeOptions - { - FastForwardStrategy = FastForwardStrategy.FastForwardOnly, - FailOnConflict = true - }); - - if (mergeResult.Status == MergeStatus.UpToDate) - return true; - - commands.Checkout( - libGitRepo, - new CheckoutOptions - { - CheckoutModifiers = CheckoutModifiers.Force - }, - startSha); - - return false; - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// public Task GetTestMerge( @@ -811,16 +757,138 @@ namespace Tgstation.Server.Host.Components.Repository cancellationToken); /// - public Task TimestampCommit(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public Task TimestampCommit(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (sha == null) + throw new ArgumentNullException(nameof(sha)); + + var commit = libGitRepo.Lookup(sha); + if (commit == null) + throw new JobException($"Commit {sha} does not exist in the repository!"); + + return commit.Committer.When; + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + /// Runs a blocking force checkout to . + /// + /// The committish to checkout. + /// Progress reporter . + /// The for the operation. + void RawCheckout(string committish, Action progressReporter, CancellationToken cancellationToken) { - if (sha == null) - throw new ArgumentNullException(nameof(sha)); + logger.LogTrace("Checkout: {0}", committish); - var commit = libGitRepo.Lookup(sha); - if (commit == null) - throw new JobException($"Commit {sha} does not exist in the repository!"); + progressReporter(0); + cancellationToken.ThrowIfCancellationRequested(); - return commit.Committer.When; - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + var checkoutOptions = new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force, + OnCheckoutProgress = CheckoutProgressHandler(progressReporter), + }; + + void RunCheckout() => commands.Checkout( + libGitRepo, + checkoutOptions, + committish); + + try + { + RunCheckout(); + } + catch (NotFoundException) + { + // Maybe (likely) a remote? + var remoteName = $"origin/{committish}"; + var potentialBranch = libGitRepo.Branches.FirstOrDefault( + branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal)); + cancellationToken.ThrowIfCancellationRequested(); + + if (potentialBranch == default) + throw; + + logger.LogDebug("Creating local branch for {0}...", potentialBranch.FriendlyName); + libGitRepo.CreateBranch(committish, potentialBranch.Tip); + cancellationToken.ThrowIfCancellationRequested(); + + RunCheckout(); + } + + cancellationToken.ThrowIfCancellationRequested(); + + libGitRepo.RemoveUntrackedFiles(); + } + + /// + /// Force push the current repository HEAD to ;. + /// + /// The username to fetch from the origin repository. + /// The password to fetch from the origin repository. + /// to report 0-100 progress of the operation. + /// The for the operation. + /// A representing the running operation. + Task PushHeadToTemporaryBranch(string username, string password, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + logger.LogInformation("Pushing changes to temporary remote branch..."); + var branch = libGitRepo.CreateBranch(RemoteTemporaryBranchName); + try + { + cancellationToken.ThrowIfCancellationRequested(); + var remote = libGitRepo.Network.Remotes.First(); + try + { + var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName); + libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken)); + var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName); + libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken)); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + catch (LibGit2SharpException e) + { + logger.LogWarning(e, "Unable to push to temporary branch!"); + } + } + finally + { + libGitRepo.Branches.Remove(branch); + } + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + /// Generate a standard set of . + /// + /// to report 0-100 progress of the operation. + /// The username for the . + /// The password for the . + /// The for the operation. + /// A new set of . + PushOptions GeneratePushOptions(Action progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions + { + OnPackBuilderProgress = (stage, current, total) => + { + var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25; + progressReporter(baseProgress + ((int)(25 * ((float)current) / total))); + return !cancellationToken.IsCancellationRequested; + }, + OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, + OnPushTransferProgress = (a, sentBytes, totalBytes) => + { + progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes))); + return !cancellationToken.IsCancellationRequested; + }, + CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password), + }; } } diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 6e025f8b6f..449e05b038 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -1,8 +1,10 @@ -using LibGit2Sharp; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using LibGit2Sharp; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Core; @@ -21,7 +23,7 @@ namespace Tgstation.Server.Host.Components.Repository public bool CloneInProgress { get; private set; } /// - /// The for the + /// The for the . /// readonly ILibGit2RepositoryFactory repositoryFactory; @@ -31,45 +33,45 @@ namespace Tgstation.Server.Host.Components.Repository readonly ILibGit2Commands commands; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IEventConsumer eventConsumer; /// - /// The for the + /// The for the . /// readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory; /// - /// The created s + /// The created s. /// readonly ILogger repositoryLogger; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Used for controlling single access to the + /// Used for controlling single access to the . /// readonly SemaphoreSlim semaphore; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . /// The value of . - /// The value of - /// The value of + /// The value of . + /// The value of . /// The value of . - /// The value of - /// The value of + /// The value of . + /// The value of . public RepositoryManager( ILibGit2RepositoryFactory repositoryFactory, ILibGit2Commands commands, @@ -141,7 +143,7 @@ namespace Tgstation.Server.Host.Components.Repository OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, BranchName = initialBranch, - CredentialsProvider = repositoryFactory.GenerateCredentialsHandler(username, password) + CredentialsProvider = repositoryFactory.GenerateCredentialsHandler(username, password), }; await repositoryFactory.Clone( diff --git a/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs b/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs index c894ce21d2..74ea6b4c54 100644 --- a/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs +++ b/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Components.Session { /// - /// Status of DMAPI validation + /// Status of DMAPI validation. /// enum ApiValidationStatus { diff --git a/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs b/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs index f5606822b2..417934f04a 100644 --- a/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs +++ b/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs @@ -1,4 +1,5 @@ using System; + using Tgstation.Server.Host.Components.Interop.Topic; namespace Tgstation.Server.Host.Components.Session @@ -19,7 +20,7 @@ namespace Tgstation.Server.Host.Components.Session public TopicResponse InteropResponse { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The optional value of . diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index 9c7a43cb3a..c06f18044c 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop.Topic; using Tgstation.Server.Host.Models; @@ -9,22 +10,22 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Session { /// - /// Handles communication with a DreamDaemon + /// Handles communication with a DreamDaemon . /// interface ISessionController : IProcessBase, IRenameNotifyee, IAsyncDisposable { /// - /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes + /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes. /// Task LaunchResult { get; } /// - /// If the DreamDaemon instance sent a + /// If the DreamDaemon instance sent a. /// bool TerminationWasRequested { get; } /// - /// The DMAPI + /// The DMAPI . /// ApiValidationStatus ApiValidationStatus { get; } @@ -39,12 +40,12 @@ namespace Tgstation.Server.Host.Components.Session CompileJob CompileJob { get; } /// - /// If the port should be rotated off when the world reboots + /// If the port should be rotated off when the world reboots. /// bool ClosePortOnReboot { get; set; } /// - /// The current + /// The current . /// RebootState RebootState { get; } @@ -54,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Session Task OnReboot { get; } /// - /// A that completes when the server calls /world/TgsInitializationComplete() + /// A that completes when the server calls /world/TgsInitializationComplete(). /// Task OnPrime { get; } @@ -64,42 +65,42 @@ namespace Tgstation.Server.Host.Components.Session bool DMApiAvailable { get; } /// - /// Releases the without terminating it. Also calls + /// Releases the without terminating it. Also calls . /// /// A resulting in which can be used to create a new . Task Release(); /// - /// Sends a command to DreamDaemon through /world/Topic() + /// Sends a command to DreamDaemon through /world/Topic(). /// /// The to send. - /// The for the operation - /// A resulting in the of /world/Topic() + /// The for the operation. + /// A resulting in the of /world/Topic(). Task SendCommand(TopicParameters parameters, CancellationToken cancellationToken); /// - /// Causes the world to start listening on a + /// Causes the world to start listening on a . /// - /// The port to change to - /// The for the operation - /// A resulting in if the operation succeeded, otherwise + /// The port to change to. + /// The for the operation. + /// A resulting in if the operation succeeded, otherwise. Task SetPort(ushort newPort, CancellationToken cancellatonToken); /// - /// Attempts to change the current to + /// Attempts to change the current to . /// - /// The new - /// The for the operation - /// A resulting in if the operation succeeded, otherwise + /// The new . + /// The for the operation. + /// A resulting in if the operation succeeded, otherwise. Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken); /// - /// Changes to without telling the DMAPI + /// Changes to without telling the DMAPI. /// void ResetRebootState(); /// - /// Enables the reading of custom chat commands from the + /// Enables the reading of custom chat commands from the . /// void EnableCustomChatCommands(); diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs index 1248cc8b9d..7e99d8e09a 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs @@ -1,5 +1,6 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Deployment; @@ -7,19 +8,19 @@ using Tgstation.Server.Host.Components.Deployment; namespace Tgstation.Server.Host.Components.Session { /// - /// Factory for s + /// Factory for s. /// interface ISessionControllerFactory { /// - /// Create a from a freshly launch DreamDaemon instance + /// Create a from a freshly launch DreamDaemon instance. /// - /// The to use - /// The current if any + /// The to use. + /// The current if any. /// The to use. will be updated with the minumum required security level for the launch. - /// If the should only validate the DMAPI then exit - /// The for the operation - /// A resulting in a new + /// If the should only validate the DMAPI then exit. + /// The for the operation. + /// A resulting in a new . Task LaunchNew( IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, @@ -28,11 +29,11 @@ namespace Tgstation.Server.Host.Components.Session CancellationToken cancellationToken); /// - /// Create a from an existing DreamDaemon instance + /// Create a from an existing DreamDaemon instance. /// - /// The to use - /// The for the operation - /// A resulting in a new on success or on failure to reattach + /// The to use. + /// The for the operation. + /// A resulting in a new on success or on failure to reattach. Task Reattach( ReattachInformation reattachInformation, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs b/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs index 6ea10b9e9d..4dc2f4c0ab 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs @@ -9,18 +9,18 @@ namespace Tgstation.Server.Host.Components.Session public interface ISessionPersistor { /// - /// Save some + /// Save some . /// /// The to save. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken); /// /// Load a saved . /// - /// The for the operation + /// The for the operation. /// A resulting in the stored if any. Task Load(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Session/ITopicClientFactory.cs b/src/Tgstation.Server.Host/Components/Session/ITopicClientFactory.cs index 2a02a0170f..c4be5235a1 100644 --- a/src/Tgstation.Server.Host/Components/Session/ITopicClientFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/ITopicClientFactory.cs @@ -1,10 +1,11 @@ -using Byond.TopicSender; -using System; +using System; + +using Byond.TopicSender; namespace Tgstation.Server.Host.Components.Session { /// - /// Factory for s + /// Factory for s. /// interface ITopicClientFactory { diff --git a/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs index c55880b09a..fd5785fd41 100644 --- a/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs @@ -1,20 +1,20 @@ -using System; +using System; using System.Globalization; namespace Tgstation.Server.Host.Components.Session { /// - /// Represents the result of trying to start a DD process + /// Represents the result of trying to start a DD process. /// public sealed class LaunchResult { /// - /// The time it took for to return or the initial bridge request to process. If the startup timed out + /// The time it took for to return or the initial bridge request to process. If the startup timed out. /// public TimeSpan? StartupTime { get; set; } /// - /// The if it exited + /// The if it exited. /// public int? ExitCode { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs index ecfb542c55..eeeea2c475 100644 --- a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs @@ -1,4 +1,5 @@ using System; + using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Models; @@ -7,12 +8,12 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Session { /// - /// Parameters necessary for duplicating a session + /// Parameters necessary for duplicating a session. /// public sealed class ReattachInformation : ReattachInformationBase { /// - /// The used by DreamDaemon + /// The used by DreamDaemon. /// public IDmbProvider Dmb { get; set; } @@ -32,7 +33,24 @@ namespace Tgstation.Server.Host.Components.Session readonly object runtimeInformationLock; /// - /// Initializes a new isntance of the . + /// Initializes a new instance of the class. For use with a given and . + /// + /// The to copy values from. + /// The value of . + /// The value of . + public ReattachInformation( + Models.ReattachInformation copy, + IDmbProvider dmb, + TimeSpan topicRequestTimeout) : base(copy) + { + Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb)); + TopicRequestTimeout = topicRequestTimeout; + + runtimeInformationLock = new object(); + } + + /// + /// Initializes a new instance of the class. /// /// The value of . /// The used to get the . @@ -60,23 +78,6 @@ namespace Tgstation.Server.Host.Components.Session runtimeInformationLock = new object(); } - /// - /// Construct a from a given and - /// - /// The to copy values from - /// The value of - /// The value of . - public ReattachInformation( - Models.ReattachInformation copy, - IDmbProvider dmb, - TimeSpan topicRequestTimeout) : base(copy) - { - Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb)); - TopicRequestTimeout = topicRequestTimeout; - - runtimeInformationLock = new object(); - } - /// /// Set the post construction. /// @@ -95,4 +96,4 @@ namespace Tgstation.Server.Host.Components.Session } } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Session/RebootState.cs b/src/Tgstation.Server.Host/Components/Session/RebootState.cs index 2368f4a82b..0b58f492dd 100644 --- a/src/Tgstation.Server.Host/Components/Session/RebootState.cs +++ b/src/Tgstation.Server.Host/Components/Session/RebootState.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Components.Session { /// - /// Represents the action to take when /world/Reboot() is called + /// Represents the action to take when /world/Reboot() is called. /// public enum RebootState : int { @@ -18,6 +18,6 @@ /// /// Restart the DreamDaemon process /// - Restart = 2 + Restart = 2, } } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 0de14b9df4..a7a5d71478 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -1,14 +1,16 @@ -using Byond.TopicSender; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Serilog.Context; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; + +using Byond.TopicSender; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Serilog.Context; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Byond; @@ -69,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Session public bool DMApiAvailable => reattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.InteropVersion.Major; /// - /// The up to date + /// The up to date . /// readonly ReattachInformation reattachInformation; @@ -89,37 +91,37 @@ namespace Tgstation.Server.Host.Components.Session readonly CancellationTokenSource reattachTopicCts; /// - /// The for the + /// The for the . /// readonly ITopicClient byondTopicSender; /// - /// The for the + /// The for the . /// readonly IBridgeRegistration bridgeRegistration; /// - /// The for the + /// The for the . /// readonly IProcess process; /// - /// The for the + /// The for the . /// readonly IByondExecutableLock byondLock; /// - /// The for the + /// The for the . /// readonly IChatTrackingContext chatTrackingContext; /// - /// The for the + /// The for the . /// readonly IChatManager chat; /// - /// The for the + /// The for the . /// readonly ILogger logger; @@ -129,17 +131,17 @@ namespace Tgstation.Server.Host.Components.Session readonly object synchronizationLock; /// - /// The waits on when DreamDaemon currently has it's ports closed + /// The waits on when DreamDaemon currently has it's ports closed. /// TaskCompletionSource portAssignmentTcs; /// - /// The port to assign DreamDaemon when it queries for it + /// The port to assign DreamDaemon when it queries for it. /// ushort? nextPort; /// - /// The that completes when DD tells us about a reboot + /// The that completes when DD tells us about a reboot. /// TaskCompletionSource rebootTcs; @@ -149,40 +151,40 @@ namespace Tgstation.Server.Host.Components.Session TaskCompletionSource primeTcs; /// - /// If we know DreamDaemon currently has it's port closed + /// If we know DreamDaemon currently has it's port closed. /// bool portClosedForReboot; /// - /// If the has been disposed + /// If the has been disposed. /// bool disposed; /// - /// The for the + /// The for the . /// ApiValidationStatus apiValidationStatus; /// - /// If should be kept alive instead + /// If should be kept alive instead. /// bool released; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . /// The owning . - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . /// The used to populate . - /// The value of - /// The value of + /// The value of . + /// The value of . /// The for the . - /// The value of + /// The value of . /// The returning a to be run after the ends. - /// The optional time to wait before failing the + /// The optional time to wait before failing the . /// If this is a reattached session. /// If this is a DMAPI validation session. public SessionController( @@ -286,68 +288,6 @@ namespace Tgstation.Server.Host.Components.Session } } - /// - /// The for . - /// - /// The . - /// The, optional, startup timeout in seconds. - /// If DreamDaemon was reattached. - /// If this is a DMAPI validation session. - /// A resulting in the for the operation. - async Task GetLaunchResult( - IAssemblyInformationProvider assemblyInformationProvider, - uint? startupTimeout, - bool reattached, - bool apiValidate) - { - var startTime = DateTimeOffset.UtcNow; - var useBridgeRequestForLaunchResult = !reattached && (apiValidate || DMApiAvailable); - var startupTask = useBridgeRequestForLaunchResult - ? initialBridgeRequestTcs.Task - : process.Startup; - var toAwait = Task.WhenAny(startupTask, process.Lifetime); - - if (startupTimeout.HasValue) - toAwait = Task.WhenAny(toAwait, Task.Delay(TimeSpan.FromSeconds(startupTimeout.Value))); - - logger.LogTrace( - "Waiting for LaunchResult based on {0}{1}...", - useBridgeRequestForLaunchResult ? "initial bridge request" : "process startup", - startupTimeout.HasValue ? $" with a timeout of {startupTimeout.Value}s" : String.Empty); - - await toAwait.ConfigureAwait(false); - - var result = new LaunchResult - { - ExitCode = process.Lifetime.IsCompleted ? (int?)await process.Lifetime.ConfigureAwait(false) : null, - StartupTime = startupTask.IsCompleted ? (TimeSpan?)(DateTimeOffset.UtcNow - startTime) : null - }; - - logger.LogTrace("Launch result: {0}", result); - - if (!result.ExitCode.HasValue && reattached && !disposed) - { - var reattachResponse = await SendCommand( - new TopicParameters( - assemblyInformationProvider.Version, - reattachInformation.RuntimeInformation.ServerPort), - reattachTopicCts.Token) - .ConfigureAwait(false); - - if (reattachResponse != null) - { - if (reattachResponse.InteropResponse?.CustomCommands != null) - chatTrackingContext.CustomCommands = reattachResponse.InteropResponse.CustomCommands; - else if (reattachResponse.InteropResponse != null) - logger.LogWarning( - "DMAPI v{0} isn't returning the TGS custom commands list. Functionality added in v5.2.0.", - CompileJob.DMApiVersion.Semver()); - } - } - - return result; - } - /// public Task ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken) { @@ -356,7 +296,7 @@ namespace Tgstation.Server.Host.Components.Session static Task Error(string message) => Task.FromResult(new BridgeResponse { - ErrorMessage = message + ErrorMessage = message, }); using (LogContext.PushProperty("Instance", metadata.Id)) @@ -492,15 +432,6 @@ namespace Tgstation.Server.Host.Components.Session } } - /// - /// Throws an if has been called - /// - void CheckDisposed() - { - if (disposed) - throw new ObjectDisposedException(nameof(SessionController)); - } - /// public void EnableCustomChatCommands() => chatTrackingContext.Active = DMApiAvailable; @@ -546,7 +477,8 @@ namespace Tgstation.Server.Host.Components.Session logger.LogTrace("Topic request: {0}", json); try { - var commandString = String.Format(CultureInfo.InvariantCulture, + var commandString = String.Format( + CultureInfo.InvariantCulture, "?{0}={1}", byondTopicSender.SanitizeString(DMApiConstants.TopicData), byondTopicSender.SanitizeString(json)); @@ -572,7 +504,7 @@ namespace Tgstation.Server.Host.Components.Session logger.LogTrace("Interop response: {0}", topicReturn); } - catch(Exception ex) + catch (Exception ex) { logger.LogWarning(ex, "Invalid interop response: {0}", topicReturn); } @@ -690,5 +622,76 @@ namespace Tgstation.Server.Host.Components.Session /// public Task CreateDump(string outputFile, CancellationToken cancellationToken) => process.CreateDump(outputFile, cancellationToken); + + /// + /// The for . + /// + /// The . + /// The, optional, startup timeout in seconds. + /// If DreamDaemon was reattached. + /// If this is a DMAPI validation session. + /// A resulting in the for the operation. + async Task GetLaunchResult( + IAssemblyInformationProvider assemblyInformationProvider, + uint? startupTimeout, + bool reattached, + bool apiValidate) + { + var startTime = DateTimeOffset.UtcNow; + var useBridgeRequestForLaunchResult = !reattached && (apiValidate || DMApiAvailable); + var startupTask = useBridgeRequestForLaunchResult + ? initialBridgeRequestTcs.Task + : process.Startup; + var toAwait = Task.WhenAny(startupTask, process.Lifetime); + + if (startupTimeout.HasValue) + toAwait = Task.WhenAny(toAwait, Task.Delay(TimeSpan.FromSeconds(startupTimeout.Value))); + + logger.LogTrace( + "Waiting for LaunchResult based on {0}{1}...", + useBridgeRequestForLaunchResult ? "initial bridge request" : "process startup", + startupTimeout.HasValue ? $" with a timeout of {startupTimeout.Value}s" : String.Empty); + + await toAwait.ConfigureAwait(false); + + var result = new LaunchResult + { + ExitCode = process.Lifetime.IsCompleted ? (int?)await process.Lifetime.ConfigureAwait(false) : null, + StartupTime = startupTask.IsCompleted ? (TimeSpan?)(DateTimeOffset.UtcNow - startTime) : null, + }; + + logger.LogTrace("Launch result: {0}", result); + + if (!result.ExitCode.HasValue && reattached && !disposed) + { + var reattachResponse = await SendCommand( + new TopicParameters( + assemblyInformationProvider.Version, + reattachInformation.RuntimeInformation.ServerPort), + reattachTopicCts.Token) + .ConfigureAwait(false); + + if (reattachResponse != null) + { + if (reattachResponse.InteropResponse?.CustomCommands != null) + chatTrackingContext.CustomCommands = reattachResponse.InteropResponse.CustomCommands; + else if (reattachResponse.InteropResponse != null) + logger.LogWarning( + "DMAPI v{0} isn't returning the TGS custom commands list. Functionality added in v5.2.0.", + CompileJob.DMApiVersion.Semver()); + } + } + + return result; + } + + /// + /// Throws an if has been called. + /// + void CheckDisposed() + { + if (disposed) + throw new ObjectDisposedException(nameof(SessionController)); + } } } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index ae6fe179d3..f736af97e5 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -1,11 +1,13 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; @@ -28,47 +30,47 @@ namespace Tgstation.Server.Host.Components.Session sealed class SessionControllerFactory : ISessionControllerFactory { /// - /// The for the + /// The for the . /// readonly IProcessExecutor processExecutor; /// - /// The for the + /// The for the . /// readonly IByondManager byond; /// - /// The for the + /// The for the . /// readonly ITopicClientFactory topicClientFactory; /// - /// The for the + /// The for the . /// readonly ICryptographySuite cryptographySuite; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IChatManager chat; /// - /// The for the + /// The for the . /// readonly INetworkPromptReaper networkPromptReaper; /// - /// The for the + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; @@ -83,32 +85,30 @@ namespace Tgstation.Server.Host.Components.Session readonly IServerPortProvider serverPortProvider; /// - /// The for the + /// The for the . /// readonly IEventConsumer eventConsumer; /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly Api.Models.Instance instance; - - /// - /// Change a given into the appropriate DreamDaemon command line word + /// Change a given into the appropriate DreamDaemon command line word. /// - /// The level to change - /// A representation of the command line parameter + /// The level to change. + /// A representation of the command line parameter. static string SecurityWord(DreamDaemonSecurity securityLevel) { return securityLevel switch @@ -138,21 +138,21 @@ namespace Tgstation.Server.Host.Components.Session } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . /// The value of . - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The value of . - /// The value of + /// The value of . /// The value of . /// The value of . public SessionControllerFactory( @@ -190,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Session } /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public async Task LaunchNew( IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, @@ -316,7 +316,8 @@ namespace Tgstation.Server.Host.Components.Session var ddOutput = await GetDDOutput().ConfigureAwait(false); logger.LogTrace( "DreamDaemon Output:{0}{1}", - Environment.NewLine, ddOutput); + Environment.NewLine, + ddOutput); } catch (Exception ex) { @@ -363,7 +364,7 @@ namespace Tgstation.Server.Host.Components.Session EventType.DreamDaemonLaunch, new List { - process.Id.ToString(CultureInfo.InvariantCulture) + process.Id.ToString(CultureInfo.InvariantCulture), }, cancellationToken) .ConfigureAwait(false); @@ -393,7 +394,7 @@ namespace Tgstation.Server.Host.Components.Session throw; } } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// public async Task Reattach( @@ -476,7 +477,7 @@ namespace Tgstation.Server.Host.Components.Session /// The . /// The level if any. /// The value of . - /// A new . + /// A new class. RuntimeInformation CreateRuntimeInformation( IDmbProvider dmbProvider, IChatTrackingContext chatTrackingContext, @@ -510,7 +511,7 @@ namespace Tgstation.Server.Host.Components.Session var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken).ConfigureAwait(false); var otherUserName = await otherUsernameTask.ConfigureAwait(false); - if(otherUserName.Equals(ourUserName, StringComparison.Ordinal)) + if (otherUserName.Equals(ourUserName, StringComparison.Ordinal)) throw new JobException(ErrorCode.DeploymentPagerRunning); } } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs index 0a96bef1e3..c8cdcb5cd2 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs @@ -1,12 +1,15 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.System; + using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Components.Session @@ -15,12 +18,12 @@ namespace Tgstation.Server.Host.Components.Session sealed class SessionPersistor : ISessionPersistor { /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly IDmbFactory dmbFactory; @@ -30,23 +33,23 @@ namespace Tgstation.Server.Host.Components.Session readonly IProcessExecutor processExecutor; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly Api.Models.Instance metadata; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . public SessionPersistor( IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, @@ -83,7 +86,7 @@ namespace Tgstation.Server.Host.Components.Session Port = reattachInformation.Port, ProcessId = reattachInformation.ProcessId, RebootState = reattachInformation.RebootState, - LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel + LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel, }; db.ReattachInformations.Add(dbReattachInfo); diff --git a/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs b/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs index 0ffa57542d..559ffd0400 100644 --- a/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs @@ -1,6 +1,7 @@ -using Byond.TopicSender; +using System; + +using Byond.TopicSender; using Microsoft.Extensions.Logging; -using System; namespace Tgstation.Server.Host.Components.Session { @@ -13,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Session readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public TopicClientFactory(ILogger logger) @@ -29,7 +30,7 @@ namespace Tgstation.Server.Host.Components.Session ConnectTimeout = timeout, DisconnectTimeout = timeout, ReceiveTimeout = timeout, - SendTimeout = timeout + SendTimeout = timeout, }, logger); } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 6dd9e2b682..9fcb712f26 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -8,6 +7,9 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components.Events; @@ -23,18 +25,39 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// sealed class Configuration : IConfiguration { + /// + /// The CodeModifications directory name. + /// const string CodeModificationsSubdirectory = "CodeModifications"; + + /// + /// The EventScripts directory name. + /// const string EventScriptsSubdirectory = "EventScripts"; + + /// + /// The GameStaticFiles directory name. + /// const string GameStaticFilesSubdirectory = "GameStaticFiles"; /// - /// Name of the ignore file in + /// Name of the ignore file in . /// const string StaticIgnoreFile = ".tgsignore"; + /// + /// The HeadInclude.dm filename. + /// const string CodeModificationsHeadFile = "HeadInclude.dm"; + + /// + /// The TailInclude.dm filename. + /// const string CodeModificationsTailFile = "TailInclude.dm"; + /// + /// Map of s to the filename of the event scripts they trigger. + /// static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary( Enum.GetValues(typeof(EventType)) .OfType() @@ -49,32 +72,32 @@ namespace Tgstation.Server.Host.Components.StaticFiles .ScriptName))); /// - /// The for + /// The for . /// readonly IIOManager ioManager; /// - /// The for + /// The for . /// readonly ISynchronousIOManager synchronousIOManager; /// - /// The for + /// The for . /// readonly ISymlinkFactory symlinkFactory; /// - /// The for + /// The for . /// readonly IProcessExecutor processExecutor; /// - /// The for + /// The for . /// readonly IPostWriteHandler postWriteHandler; /// - /// The for + /// The for . /// readonly IPlatformIdentifier platformIdentifier; @@ -84,7 +107,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles readonly IFileTransferTicketProvider fileTransferService; /// - /// The for + /// The for . /// readonly ILogger logger; @@ -104,16 +127,16 @@ namespace Tgstation.Server.Host.Components.StaticFiles Task uploadTasks; /// - /// Construct + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . - /// The value of + /// The value of . public Configuration( IIOManager ioManager, ISynchronousIOManager synchronousIOManager, @@ -146,30 +169,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles disposeCts.Dispose(); } - /// - /// Get the proper path to - /// - /// The relative path to - string StaticIgnorePath() => ioManager.ConcatPath(GameStaticFilesSubdirectory, StaticIgnoreFile); - - /// - /// Ensures standard configuration directories exist - /// - /// The for the operation - /// A representing the running operation - async Task EnsureDirectories(CancellationToken cancellationToken) - { - async Task ValidateStaticFolder() - { - await ioManager.CreateDirectory(GameStaticFilesSubdirectory, cancellationToken).ConfigureAwait(false); - var staticIgnorePath = StaticIgnorePath(); - if(!await ioManager.FileExists(staticIgnorePath, cancellationToken).ConfigureAwait(false)) - await ioManager.WriteAllBytes(staticIgnorePath, Array.Empty(), cancellationToken).ConfigureAwait(false); - } - - await Task.WhenAll(ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken), ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken), ValidateStaticFolder()).ConfigureAwait(false); - } - /// public async Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken) { @@ -200,20 +199,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles } } - string ValidateConfigRelativePath(string configurationRelativePath) - { - var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath); - if (nullOrEmptyCheck) - configurationRelativePath = DefaultIOManager.CurrentDirectory; - if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar) - configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath; - var resolved = ioManager.ResolvePath(configurationRelativePath); - var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null; - if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois - throw new InvalidOperationException("Attempted to access file outside of configuration manager!"); - return resolved; - } - /// public async Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { @@ -323,7 +308,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles IsDirectory = false, LastReadHash = originalSha, AccessDenied = false, - Path = configurationRelativePath + Path = configurationRelativePath, }; } catch (UnauthorizedAccessException) @@ -334,7 +319,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles { isDirectory = synchronousIOManager.IsDirectory(path); } - catch(Exception ex) + catch (Exception ex) { logger.LogDebug(ex, "IsDirectory exception!"); isDirectory = false; @@ -342,7 +327,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles result = new ConfigurationFileResponse { - Path = configurationRelativePath + Path = configurationRelativePath, }; if (!isDirectory) result.AccessDenied = true; @@ -466,9 +451,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles if (!success) fileTicket.SetErrorMessage(new ErrorMessageResponse(ErrorCode.ConfigurationFileUpdated) { - AdditionalData = fileHash + AdditionalData = fileHash, }); - else if(uploadStream.Length > 0) + else if (uploadStream.Length > 0) postWriteHandler.HandleWrite(path); } } @@ -479,7 +464,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles LastReadHash = previousHash, IsDirectory = false, AccessDenied = false, - Path = configurationRelativePath + Path = configurationRelativePath, }; lock (disposeCts) @@ -493,7 +478,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles { isDirectory = synchronousIOManager.IsDirectory(path); } - catch(Exception ex) + catch (Exception ex) { logger.LogDebug(ex, "IsDirectory exception!"); isDirectory = false; @@ -501,7 +486,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles result = new ConfigurationFileResponse { - Path = configurationRelativePath + Path = configurationRelativePath, }; if (!isDirectory) result.AccessDenied = true; @@ -571,11 +556,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles return; } - foreach (var I in scriptFiles) + foreach (var scriptFile in scriptFiles) { - logger.LogTrace("Running event script {0}...", I); + logger.LogTrace("Running event script {0}...", scriptFile); using (var script = processExecutor.LaunchProcess( - ioManager.ConcatPath(resolvedScriptsDir, I), + ioManager.ConcatPath(resolvedScriptsDir, scriptFile), resolvedScriptsDir, String.Join( ' ', @@ -597,7 +582,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles cancellationToken.ThrowIfCancellationRequested(); var scriptOutput = await script.GetCombinedOutput(cancellationToken).ConfigureAwait(false); if (exitCode != 0) - throw new JobException($"Script {I} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}"); + throw new JobException($"Script {scriptFile} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}"); else logger.LogDebug("Script output:{0}{1}", Environment.NewLine, scriptOutput); } @@ -624,5 +609,48 @@ namespace Tgstation.Server.Host.Components.StaticFiles return result; } + + /// + /// Get the proper path to . + /// + /// The relative path to . + string StaticIgnorePath() => ioManager.ConcatPath(GameStaticFilesSubdirectory, StaticIgnoreFile); + + /// + /// Ensures standard configuration directories exist. + /// + /// The for the operation. + /// A representing the running operation. + async Task EnsureDirectories(CancellationToken cancellationToken) + { + async Task ValidateStaticFolder() + { + await ioManager.CreateDirectory(GameStaticFilesSubdirectory, cancellationToken).ConfigureAwait(false); + var staticIgnorePath = StaticIgnorePath(); + if (!await ioManager.FileExists(staticIgnorePath, cancellationToken).ConfigureAwait(false)) + await ioManager.WriteAllBytes(staticIgnorePath, Array.Empty(), cancellationToken).ConfigureAwait(false); + } + + await Task.WhenAll(ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken), ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken), ValidateStaticFolder()).ConfigureAwait(false); + } + + /// + /// Resolve a given to it's full path or throw an if it violates rules. + /// + /// A relative path in the instance's configuration directory. + /// The full on-disk path of . + string ValidateConfigRelativePath(string configurationRelativePath) + { + var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath); + if (nullOrEmptyCheck) + configurationRelativePath = DefaultIOManager.CurrentDirectory; + if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar) + configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath; + var resolved = ioManager.ResolvePath(configurationRelativePath); + var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null; + if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois + throw new InvalidOperationException("Attempted to access file outside of configuration manager!"); + return resolved; + } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 59439fae24..2758ba15fc 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components.Events; @@ -11,71 +13,71 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components.StaticFiles { /// - /// For managing the Configuration directory + /// For managing the Configuration directory. /// public interface IConfiguration : IHostedService, IEventConsumer, IDisposable { /// - /// Copies all files in the CodeModifications directory to + /// Copies all files in the CodeModifications directory to . /// - /// The .dme file being compiled - /// Path to the destination folder - /// The for the operation - /// A resulting in the if any + /// The .dme file being compiled. + /// Path to the destination folder. + /// The for the operation. + /// A resulting in the if any. Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken); /// - /// Symlinks all directories in the GameData directory to + /// Symlinks all directories in the GameData directory to . /// - /// Path to the destination folder - /// The for the operation - /// A representing the running operation + /// Path to the destination folder. + /// The for the operation. + /// A representing the running operation. Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken); /// - /// Get s for all items in a given + /// Get s for all items in a given . /// - /// The relative path in the Configuration directory - /// The for the operation. If , the operation will be performed as the user of the - /// The for the operation - /// A resulting in the s for the items in the directory. and will both be + /// The relative path in the Configuration directory. + /// The for the operation. If , the operation will be performed as the user of the . + /// The for the operation. + /// A resulting in the s for the items in the directory. and will both be . Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// Reads a given + /// Reads a given . /// - /// The relative path in the Configuration directory - /// The for the operation. If , the operation will be performed as the user of the - /// The for the operation - /// A resulting in the of the file + /// The relative path in the Configuration directory. + /// The for the operation. If , the operation will be performed as the user of the . + /// The for the operation. + /// A resulting in the of the file. Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// Create an empty directory at + /// Create an empty directory at . /// - /// The relative path in the Configuration directory - /// The for the operation. If , the operation will be performed as the user of the - /// The for the operation. Usage may result in partial writes - /// A resulting in if the directory already existed, otherwise + /// The relative path in the Configuration directory. + /// The for the operation. If , the operation will be performed as the user of the . + /// The for the operation. Usage may result in partial writes. + /// A resulting in if the directory already existed, otherwise. Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// Attempt to delete an empty directory at + /// Attempt to delete an empty directory at . /// - /// The path of the empty directory to delete - /// The for the operation. If , the operation will be performed as the user of the - /// The for the operation - /// if the directory was empty and deleted, otherwise + /// The path of the empty directory to delete. + /// The for the operation. If , the operation will be performed as the user of the . + /// The for the operation. + /// if the directory was empty and deleted, otherwise. Task DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// Writes to a given + /// Writes to a given . /// - /// The relative path in the Configuration directory - /// The for the operation. If , the operation will be performed as the user of the - /// The hash any existing file must match in order for the write to succeed - /// The for the operation. Usage may result in partial writes - /// A resulting in the updated or if the write failed due to conflicts + /// The relative path in the Configuration directory. + /// The for the operation. If , the operation will be performed as the user of the . + /// The hash any existing file must match in order for the write to succeed. + /// The for the operation. Usage may result in partial writes. + /// A resulting in the updated or if the write failed due to conflicts. Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, string previousHash, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs index 7afb8ebf1f..6b7e358f0b 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs @@ -1,31 +1,31 @@ namespace Tgstation.Server.Host.Components.StaticFiles { /// - /// Represents code modifications via configuration + /// Represents code modifications via configuration. /// public sealed class ServerSideModifications { /// - /// If the target dme was completely overwitten + /// If the target dme was completely overwitten. /// public bool TotalDmeOverwrite { get; } /// - /// The #include line which should be added to the beginning of the .dme if any + /// The #include line which should be added to the beginning of the .dme if any. /// public string HeadIncludeLine { get; } /// - /// The #include line which should be added to the end of the .dme if any + /// The #include line which should be added to the end of the .dme if any. /// public string TailIncludeLine { get; } /// - /// Construct + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . public ServerSideModifications(string headIncludeLine, string tailIncludeLine, bool totalDmeOverwrite) { HeadIncludeLine = headIncludeLine; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 3cddd03a02..6cc3fdac2d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -38,7 +40,7 @@ namespace Tgstation.Server.Host.Components.Watchdog bool gracefulRebootRequired; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -84,7 +86,21 @@ namespace Tgstation.Server.Host.Components.Watchdog initialLaunchParameters, instance, autoStart) - { } + { + } + + /// + public override Task ResetRebootState(CancellationToken cancellationToken) + { + if (!gracefulRebootRequired) + return base.ResetRebootState(cancellationToken); + + return Restart(true, cancellationToken); + } + + /// + public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) + => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask; /// protected override async Task HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken) @@ -148,6 +164,7 @@ namespace Tgstation.Server.Host.Components.Watchdog default: throw new InvalidOperationException($"Invalid reboot state: {rebootState}"); } + case MonitorActivationReason.ActiveLaunchParametersUpdated: await Server.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false); gracefulRebootRequired = true; @@ -288,18 +305,5 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the operation. /// A resulting in the modified to be used. protected virtual Task PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse); - - /// - public override Task ResetRebootState(CancellationToken cancellationToken) - { - if (!gracefulRebootRequired) - return base.ResetRebootState(cancellationToken); - - return Restart(true, cancellationToken); - } - - /// - public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) - => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 919f3e0cf9..f0205eeb49 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Events; @@ -10,7 +12,7 @@ using Tgstation.Server.Host.Components.Session; namespace Tgstation.Server.Host.Components.Watchdog { /// - /// Runs and monitors the twin server controllers + /// Runs and monitors the twin server controllers. /// public interface IWatchdog : IHostedService, IAsyncDisposable, IEventConsumer, IRenameNotifyee { @@ -20,66 +22,66 @@ namespace Tgstation.Server.Host.Components.Watchdog WatchdogStatus Status { get; } /// - /// If the alpha server is the active server + /// If the alpha server is the active server. /// bool AlphaIsActive { get; } /// - /// Retrieves the currently running on the server + /// Retrieves the currently running on the server. /// Models.CompileJob ActiveCompileJob { get; } /// - /// The the active server is using + /// The the active server is using. /// DreamDaemonLaunchParameters ActiveLaunchParameters { get; } /// - /// The to be applied + /// The to be applied. /// DreamDaemonLaunchParameters LastLaunchParameters { get; } /// - /// The of the active server + /// The of the active server. /// RebootState? RebootState { get; } /// - /// Start the + /// Start the . /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Launch(CancellationToken cancellationToken); /// /// Changes the . If currently running, may trigger a graceful restart. /// - /// The new . May be modified - /// The for the operation - /// A representing the running operation + /// The new . May be modified. + /// The for the operation. + /// A representing the running operation. Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); /// - /// Restarts the watchdog + /// Restarts the watchdog. /// - /// If the restart will be delayed until a reboot is detected in the active server's DMAPI and this function will retrun immediately - /// The for the operation - /// A representing the running operation + /// If the restart will be delayed until a reboot is detected in the active server's DMAPI and this function will retrun immediately. + /// The for the operation. + /// A representing the running operation. Task Restart(bool graceful, CancellationToken cancellationToken); /// - /// Stops the watchdog + /// Stops the watchdog. /// - /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately - /// The for the operation - /// A representing the running operation + /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately. + /// The for the operation. + /// A representing the running operation. Task Terminate(bool graceful, CancellationToken cancellationToken); /// - /// Cancels pending graceful actions + /// Cancels pending graceful actions. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task ResetRebootState(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index b833c7812a..308cd3db54 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Deployment.Remote; @@ -9,24 +9,24 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Watchdog { /// - /// For creating s + /// For creating s. /// interface IWatchdogFactory { /// - /// Creates a + /// Creates a . /// - /// The for the - /// The for the with - /// The for the - /// The for the + /// The for the . + /// The for the with. + /// The for the . + /// The for the . /// The pointing to the Game directory for the . /// The pointing to the Diagnostics directory for the . /// The for the . /// The for the . - /// The for the - /// The initial for the - /// A new + /// The for the . + /// The initial for the . + /// A new . IWatchdog CreateWatchdog( IChatManager chat, IDmbFactory dmbFactory, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs index 3e5e26958e..50c33760bb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { /// - /// The action for the monitor loop to take when control is returned to it + /// The action for the monitor loop to take when control is returned to it. /// enum MonitorAction { @@ -28,6 +28,6 @@ /// /// The monitor should end all sessions and exit. /// - Exit + Exit, } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs index 4f1073e742..eaa4b4e441 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -1,7 +1,7 @@ -namespace Tgstation.Server.Host.Components.Watchdog +namespace Tgstation.Server.Host.Components.Watchdog { /// - /// Reasons for the monitor to wake up + /// Reasons for the monitor to wake up. /// enum MonitorActivationReason { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs index a0ef483b82..b43c9ccdde 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -25,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IDmbProvider hardLinkedDmb; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -77,7 +79,8 @@ namespace Tgstation.Server.Host.Components.Watchdog initialLaunchParameters, instance, autoStart) - { } + { + } /// protected override async Task InitialLink(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs index 92aa8ed26b..308f43ba1e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs @@ -1,6 +1,8 @@ +using System; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -20,7 +22,7 @@ namespace Tgstation.Server.Host.Components.Watchdog sealed class PosixWatchdogFactory : WindowsWatchdogFactory { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -42,7 +44,8 @@ namespace Tgstation.Server.Host.Components.Watchdog asyncDelayer, symlinkFactory, generalConfigurationOptions) - { } + { + } /// public override IWatchdog CreateWatchdog( diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index ade3400107..38f867557d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.Logging; -using Serilog.Context; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Serilog.Context; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; @@ -24,7 +26,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Base class for s. /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify abstract class WatchdogBase : IWatchdog, ICustomCommandHandler, IRestartHandler { /// @@ -54,7 +56,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public abstract RebootState? RebootState { get; } /// - /// that completes when are changed and we are running + /// that completes when are changed and we are running. /// protected TaskCompletionSource ActiveParametersUpdated { get; set; } @@ -64,17 +66,17 @@ namespace Tgstation.Server.Host.Components.Watchdog protected ILogger Logger { get; } /// - /// The for the + /// The for the . /// protected IChatManager Chat { get; } /// - /// The for the + /// The for the . /// protected ISessionControllerFactory SessionControllerFactory { get; } /// - /// The for the + /// The for the . /// protected IDmbFactory DmbFactory { get; } @@ -84,7 +86,7 @@ namespace Tgstation.Server.Host.Components.Watchdog protected IAsyncDelayer AsyncDelayer { get; } /// - /// The that is not the + /// The that is not the . /// protected IEventConsumer EventConsumer { get; } @@ -104,7 +106,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly SemaphoreSlim controllerDisposeSemaphore; /// - /// The for the + /// The for the . /// readonly ISessionPersistor sessionPersistor; @@ -129,7 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory; /// - /// If the should in + /// If the should in . /// readonly bool autoStart; @@ -139,12 +141,12 @@ namespace Tgstation.Server.Host.Components.Watchdog ReattachInformation releasedReattachInformation; /// - /// The for the monitor loop + /// The for the monitor loop. /// CancellationTokenSource monitorCts; /// - /// The running the monitor loop + /// The running the monitor loop. /// Task monitorTask; @@ -159,7 +161,7 @@ namespace Tgstation.Server.Host.Components.Watchdog int heartbeatsMissed; /// - /// If the servers should be released instead of shutdown + /// If the servers should be released instead of shutdown. /// bool releaseServers; @@ -169,22 +171,22 @@ namespace Tgstation.Server.Host.Components.Watchdog bool disposed; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The to populate with + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The to populate with. /// The value of . /// The value of . /// The value of . /// The value of . - /// The value of - /// The initial value of . May be modified - /// The value of - /// The value of + /// The value of . + /// The initial value of . May be modified. + /// The value of . + /// The value of . protected WatchdogBase( IChatManager chat, ISessionControllerFactory sessionControllerFactory, @@ -254,110 +256,238 @@ namespace Tgstation.Server.Host.Components.Watchdog disposed = true; } - /// - /// Implementation of . Does not lock - /// - /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately - /// If the termination will be announced using - /// The for the operation - /// A representing the running operation. - async Task TerminateNoLock(bool graceful, bool announce, CancellationToken cancellationToken) + /// + public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) + { + bool match = launchParameters.CanApplyWithoutReboot(ActiveLaunchParameters); + ActiveLaunchParameters = launchParameters; + if (match || Status == WatchdogStatus.Offline) + return; + + ActiveParametersUpdated.TrySetResult(null); // queue an update + ActiveParametersUpdated = new TaskCompletionSource(); + } + } + + /// + public async Task HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) + { + if (Status == WatchdogStatus.Offline) + return "TGS: Server offline!"; + + var commandObject = new ChatCommand(sender, commandName, arguments); + + var command = new TopicParameters(commandObject); + + var activeServer = GetActiveController(); + var commandResult = await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false); + + if (commandResult == null) + return "TGS: Bad topic exchange!"; + + if (commandResult.InteropResponse == null) + return "TGS: Bad topic response!"; + + return commandResult.InteropResponse.CommandResponseMessage ?? + "TGS: Command processed but no DMAPI response returned!"; + } + } + + /// + public async Task Launch(CancellationToken cancellationToken) + { + if (Status != WatchdogStatus.Offline) + throw new JobException(ErrorCode.WatchdogRunning); + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) + await LaunchNoLock(true, true, true, null, cancellationToken).ConfigureAwait(false); + } + + /// + public virtual async Task ResetRebootState(CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) + { + if (Status == WatchdogStatus.Offline) + return; + var toClear = GetActiveController(); + if (toClear != null) + await toClear.SetRebootState(Session.RebootState.Normal, cancellationToken).ConfigureAwait(false); + } + } + + /// + public async Task Restart(bool graceful, CancellationToken cancellationToken) { if (Status == WatchdogStatus.Offline) - return; - if (!graceful) + throw new JobException(ErrorCode.WatchdogNotRunning); + + Logger.LogTrace("Begin Restart. Graceful: {0}", graceful); + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) { - var eventTask = EventConsumer.HandleEvent( - releaseServers - ? EventType.WatchdogDetach - : EventType.WatchdogShutdown, - Enumerable.Empty(), - cancellationToken); + if (!graceful) + { + var chatTask = Chat.QueueWatchdogMessage("Manual restart triggered...", cancellationToken); + await TerminateNoLock(false, false, cancellationToken).ConfigureAwait(false); + await LaunchNoLock(true, false, true, null, cancellationToken).ConfigureAwait(false); + await chatTask.ConfigureAwait(false); + return; + } - var chatTask = announce ? Chat.QueueWatchdogMessage("Shutting down...", cancellationToken) : Task.CompletedTask; + var toReboot = GetActiveController(); + if (toReboot != null + && !await toReboot.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false)) + Logger.LogWarning("Unable to send reboot state change event!"); + } + } - await eventTask.ConfigureAwait(false); - - await StopMonitor().ConfigureAwait(false); - - LastLaunchParameters = null; - - await chatTask.ConfigureAwait(false); + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + var reattachInfo = await sessionPersistor.Load(cancellationToken).ConfigureAwait(false); + if (!autoStart && reattachInfo == null) return; - } - // merely set the reboot state - var toKill = GetActiveController(); - if (toKill != null) + var job = new Models.Job { - await toKill.SetRebootState(Session.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); - Logger.LogTrace("Graceful termination requested"); + Instance = new Models.Instance + { + Id = metadata.Id, + }, + Description = $"Instance startup watchdog {(reattachInfo != null ? "reattach" : "launch")}", + CancelRight = (ulong)DreamDaemonRights.Shutdown, + CancelRightsType = RightsType.DreamDaemon, + }; + await jobManager.RegisterOperation( + job, + async (core, databaseContextFactory, paramJob, progressFunction, ct) => + { + if (core.Watchdog != this) + throw new InvalidOperationException(Instance.DifferentCoreExceptionMessage); + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, ct).ConfigureAwait(false)) + await LaunchNoLock(true, true, true, reattachInfo, ct).ConfigureAwait(false); + }, + cancellationToken) + .ConfigureAwait(false); + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + await TerminateNoLock(false, !releaseServers, cancellationToken).ConfigureAwait(false); + if (releasedReattachInformation != null) + { + try + { + await sessionPersistor.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogCritical( + ex, + "Failed to persist session reattach information! To repair this, DreamDaemon will need to be manully stopped and then relaunched with TGS."); + } + + releasedReattachInformation = null; + releaseServers = false; } + } + + /// + public async Task Terminate(bool graceful, CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) + await TerminateNoLock(graceful, !releaseServers, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + releaseServers = true; + if (Status == WatchdogStatus.Online) + await Chat.QueueWatchdogMessage("Detaching...", cancellationToken).ConfigureAwait(false); else - Logger.LogTrace("Could not gracefully terminate as there is no active controller!"); + Logger.LogTrace("Not sending detach chat message as status is: {0}", Status); + } + + /// + public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); + + /// + public async Task CreateDump(CancellationToken cancellationToken) + { + const string DumpDirectory = "ProcessDumps"; + await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken).ConfigureAwait(false); + + var dumpFileName = diagnosticsIOManager.ResolvePath( + diagnosticsIOManager.ConcatPath( + DumpDirectory, + $"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}.dmp")); + + var session = GetActiveController(); + if (session?.Lifetime.IsCompleted != false) + throw new JobException(ErrorCode.DreamDaemonOffline); + + Logger.LogInformation("Dumping session to {0}...", dumpFileName); + await session.CreateDump(dumpFileName, cancellationToken).ConfigureAwait(false); + } + + /// + async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + // Method explicitly implemented to prevent accidental calls when this.eventConsumer should be used. + var activeServer = GetActiveController(); + + // Server may have ended + if (activeServer == null) + return; + + var notification = new EventNotification(eventType, parameters); + var result = await activeServer.SendCommand( + new TopicParameters(notification), + cancellationToken) + .ConfigureAwait(false); + + if (result?.InteropResponse?.ChatResponses != null) + foreach (var response in result.InteropResponse.ChatResponses) + Chat.QueueMessage( + response.Text, + response.ChannelIds + .Select(channelIdString => + { + if (UInt64.TryParse(channelIdString, out var channelId)) + return (ulong?)channelId; + + return null; + }) + .Where(nullableChannelId => nullableChannelId.HasValue) + .Select(nullableChannelId => nullableChannelId.Value)); } /// - /// Handles a watchdog heartbeat. + /// Starts all s. /// + /// A, possibly active, for an outgoing chat message. + /// to use, if any. /// The for the operation. - /// A resulting in the next to take. - async Task HandleHeartbeat(CancellationToken cancellationToken) - { - Logger.LogTrace("Sending heartbeat to active server..."); - var activeServer = GetActiveController(); - var response = await activeServer.SendCommand(new TopicParameters(), cancellationToken).ConfigureAwait(false); - - var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown; - if (response == null) - { - switch (++heartbeatsMissed) - { - case 1: - Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!"); - break; - case 2: - var message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!"; - Logger.LogInformation(message2); - await Chat.QueueWatchdogMessage(message2, cancellationToken).ConfigureAwait(false); - break; - case 3: - var actionToTake = shouldShutdown - ? "shutdown" - : "be restarted"; - var message3 = $"DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!"; - Logger.LogWarning(message3); - await Chat.QueueWatchdogMessage(message3, cancellationToken).ConfigureAwait(false); - break; - case 4: - var actionTaken = shouldShutdown - ? "Shutting down due to graceful termination request" - : "Restarting"; - var message4 = $"DEFCON 1: Four heartbeats have been missed! {actionTaken}..."; - Logger.LogWarning(message4); - await Chat.QueueWatchdogMessage(message4, cancellationToken).ConfigureAwait(false); - await DisposeAndNullControllers(cancellationToken).ConfigureAwait(false); - return shouldShutdown ? MonitorAction.Exit : MonitorAction.Restart; - default: - Logger.LogError("Invalid heartbeats missed count: {0}", heartbeatsMissed); - break; - } - } - else - heartbeatsMissed = 0; - - return MonitorAction.Continue; - } + /// A representing the running operation. + protected abstract Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken); /// /// Launches the watchdog. /// - /// If should be started by this function - /// If the launch should be announced to chat by this function + /// If should be started by this function. + /// If the launch should be announced to chat by this function. /// If launch failure should be announced to chat by this function. - /// to use, if any - /// The for the operation - /// A representing the running operation + /// to use, if any. + /// The for the operation. + /// A representing the running operation. protected async Task LaunchNoLock( bool startMonitor, bool announce, @@ -439,9 +569,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - /// Stops . Doesn't kill the servers + /// Stops . Doesn't kill the servers. /// - /// if the monitor was running, otherwise + /// if the monitor was running, otherwise. protected async Task StopMonitor() { Logger.LogTrace("StopMonitor"); @@ -521,7 +651,7 @@ namespace Tgstation.Server.Host.Components.Watchdog protected abstract ISessionController GetActiveController(); /// - /// Handles the actions to take when the monitor has to "wake up" + /// Handles the actions to take when the monitor has to "wake up". /// /// The that caused the invocation. Will never be . /// The for the operation. @@ -614,8 +744,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the operation. /// A representing the running operation. - #pragma warning disable CA1502 - private async Task MonitorLifetimes(CancellationToken cancellationToken) +#pragma warning disable CA1502 + async Task MonitorLifetimes(CancellationToken cancellationToken) { Logger.LogTrace("Entered MonitorLifetimes"); Status = WatchdogStatus.Online; @@ -812,229 +942,101 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogTrace("Monitor exiting..."); } - #pragma warning restore CA1502 +#pragma warning restore CA1502 /// - /// Starts all s. + /// Implementation of . Does not lock . /// - /// A, possibly active, for an outgoing chat message. - /// to use, if any - /// The for the operation - /// A representing the running operation - protected abstract Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken); - - /// - public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) - { - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - { - bool match = launchParameters.CanApplyWithoutReboot(ActiveLaunchParameters); - ActiveLaunchParameters = launchParameters; - if (match || Status == WatchdogStatus.Offline) - return; - - ActiveParametersUpdated.TrySetResult(null); // queue an update - ActiveParametersUpdated = new TaskCompletionSource(); - } - } - - /// - async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - // Method explicitly implemented to prevent accidental calls when this.eventConsumer should be used. - var activeServer = GetActiveController(); - - // Server may have ended - if (activeServer == null) - return; - - var notification = new EventNotification(eventType, parameters); - var result = await activeServer.SendCommand( - new TopicParameters(notification), - cancellationToken) - .ConfigureAwait(false); - - if (result?.InteropResponse?.ChatResponses != null) - foreach (var response in result.InteropResponse.ChatResponses) - Chat.QueueMessage( - response.Text, - response.ChannelIds - .Select(channelIdString => - { - if (UInt64.TryParse(channelIdString, out var channelId)) - return (ulong?)channelId; - - return null; - }) - .Where(nullableChannelId => nullableChannelId.HasValue) - .Select(nullableChannelId => nullableChannelId.Value)); - } - - /// - public async Task HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken) - { - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - { - if (Status == WatchdogStatus.Offline) - return "TGS: Server offline!"; - - var commandObject = new ChatCommand(sender, commandName, arguments); - - var command = new TopicParameters(commandObject); - - var activeServer = GetActiveController(); - var commandResult = await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false); - - if (commandResult == null) - return "TGS: Bad topic exchange!"; - - if (commandResult.InteropResponse == null) - return "TGS: Bad topic response!"; - - return commandResult.InteropResponse.CommandResponseMessage ?? - "TGS: Command processed but no DMAPI response returned!"; - } - } - - /// - public async Task Launch(CancellationToken cancellationToken) - { - if (Status != WatchdogStatus.Offline) - throw new JobException(ErrorCode.WatchdogRunning); - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - await LaunchNoLock(true, true, true, null, cancellationToken).ConfigureAwait(false); - } - - /// - public virtual async Task ResetRebootState(CancellationToken cancellationToken) - { - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - { - if (Status == WatchdogStatus.Offline) - return; - var toClear = GetActiveController(); - if (toClear != null) - await toClear.SetRebootState(Session.RebootState.Normal, cancellationToken).ConfigureAwait(false); - } - } - - /// - public async Task Restart(bool graceful, CancellationToken cancellationToken) + /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately. + /// If the termination will be announced using . + /// The for the operation. + /// A representing the running operation. + async Task TerminateNoLock(bool graceful, bool announce, CancellationToken cancellationToken) { if (Status == WatchdogStatus.Offline) - throw new JobException(ErrorCode.WatchdogNotRunning); - - Logger.LogTrace("Begin Restart. Graceful: {0}", graceful); - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - { - if (!graceful) - { - var chatTask = Chat.QueueWatchdogMessage("Manual restart triggered...", cancellationToken); - await TerminateNoLock(false, false, cancellationToken).ConfigureAwait(false); - await LaunchNoLock(true, false, true, null, cancellationToken).ConfigureAwait(false); - await chatTask.ConfigureAwait(false); - return; - } - - var toReboot = GetActiveController(); - if (toReboot != null - && !await toReboot.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false)) - Logger.LogWarning("Unable to send reboot state change event!"); - } - } - - /// - public async Task StartAsync(CancellationToken cancellationToken) - { - var reattachInfo = await sessionPersistor.Load(cancellationToken).ConfigureAwait(false); - if (!autoStart && reattachInfo == null) return; - - var job = new Models.Job + if (!graceful) { - Instance = new Models.Instance - { - Id = metadata.Id - }, - Description = $"Instance startup watchdog {(reattachInfo != null ? "reattach" : "launch")}", - CancelRight = (ulong)DreamDaemonRights.Shutdown, - CancelRightsType = RightsType.DreamDaemon - }; - await jobManager.RegisterOperation( - job, - async (core, databaseContextFactory, paramJob, progressFunction, ct) => - { - if (core.Watchdog != this) - throw new InvalidOperationException(Instance.DifferentCoreExceptionMessage); - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, ct).ConfigureAwait(false)) - await LaunchNoLock(true, true, true, reattachInfo, ct).ConfigureAwait(false); - }, - cancellationToken) - .ConfigureAwait(false); - } + var eventTask = EventConsumer.HandleEvent( + releaseServers + ? EventType.WatchdogDetach + : EventType.WatchdogShutdown, + Enumerable.Empty(), + cancellationToken); - /// - public async Task StopAsync(CancellationToken cancellationToken) - { - await TerminateNoLock(false, !releaseServers, cancellationToken).ConfigureAwait(false); - if (releasedReattachInformation != null) - { - try - { - await sessionPersistor.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogCritical( - ex, - "Failed to persist session reattach information! To repair this, DreamDaemon will need to be manully stopped and then relaunched with TGS."); - } + var chatTask = announce ? Chat.QueueWatchdogMessage("Shutting down...", cancellationToken) : Task.CompletedTask; - releasedReattachInformation = null; - releaseServers = false; + await eventTask.ConfigureAwait(false); + + await StopMonitor().ConfigureAwait(false); + + LastLaunchParameters = null; + + await chatTask.ConfigureAwait(false); + return; } - } - /// - public async Task Terminate(bool graceful, CancellationToken cancellationToken) - { - using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false)) - await TerminateNoLock(graceful, !releaseServers, cancellationToken).ConfigureAwait(false); - } - - /// - public async Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) - { - releaseServers = true; - if (Status == WatchdogStatus.Online) - await Chat.QueueWatchdogMessage("Detaching...", cancellationToken).ConfigureAwait(false); + // merely set the reboot state + var toKill = GetActiveController(); + if (toKill != null) + { + await toKill.SetRebootState(Session.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); + Logger.LogTrace("Graceful termination requested"); + } else - Logger.LogTrace("Not sending detach chat message as status is: {0}", Status); + Logger.LogTrace("Could not gracefully terminate as there is no active controller!"); } - /// - public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); - - /// - public async Task CreateDump(CancellationToken cancellationToken) + /// + /// Handles a watchdog heartbeat. + /// + /// The for the operation. + /// A resulting in the next to take. + async Task HandleHeartbeat(CancellationToken cancellationToken) { - const string DumpDirectory = "ProcessDumps"; - await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken).ConfigureAwait(false); + Logger.LogTrace("Sending heartbeat to active server..."); + var activeServer = GetActiveController(); + var response = await activeServer.SendCommand(new TopicParameters(), cancellationToken).ConfigureAwait(false); - var dumpFileName = diagnosticsIOManager.ResolvePath( - diagnosticsIOManager.ConcatPath( - DumpDirectory, - $"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}.dmp")); + var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown; + if (response == null) + { + switch (++heartbeatsMissed) + { + case 1: + Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!"); + break; + case 2: + var message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!"; + Logger.LogInformation(message2); + await Chat.QueueWatchdogMessage(message2, cancellationToken).ConfigureAwait(false); + break; + case 3: + var actionToTake = shouldShutdown + ? "shutdown" + : "be restarted"; + var message3 = $"DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!"; + Logger.LogWarning(message3); + await Chat.QueueWatchdogMessage(message3, cancellationToken).ConfigureAwait(false); + break; + case 4: + var actionTaken = shouldShutdown + ? "Shutting down due to graceful termination request" + : "Restarting"; + var message4 = $"DEFCON 1: Four heartbeats have been missed! {actionTaken}..."; + Logger.LogWarning(message4); + await Chat.QueueWatchdogMessage(message4, cancellationToken).ConfigureAwait(false); + await DisposeAndNullControllers(cancellationToken).ConfigureAwait(false); + return shouldShutdown ? MonitorAction.Exit : MonitorAction.Restart; + default: + Logger.LogError("Invalid heartbeats missed count: {0}", heartbeatsMissed); + break; + } + } + else + heartbeatsMissed = 0; - var session = GetActiveController(); - if (session?.Lifetime.IsCompleted != false) - throw new JobException(ErrorCode.DreamDaemonOffline); - - Logger.LogInformation("Dumping session to {0}...", dumpFileName); - await session.CreateDump(dumpFileName, cancellationToken).ConfigureAwait(false); + return MonitorAction.Continue; } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 2185a78586..d4a49d99a9 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,6 +1,8 @@ +using System; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -18,38 +20,38 @@ namespace Tgstation.Server.Host.Components.Watchdog class WatchdogFactory : IWatchdogFactory { /// - /// The for the + /// The for the . /// protected IServerControl ServerControl { get; } /// - /// The for the + /// The for the . /// protected ILoggerFactory LoggerFactory { get; } /// - /// The for the + /// The for the . /// protected IJobManager JobManager { get; } /// - /// The for the + /// The for the . /// protected IAsyncDelayer AsyncDelayer { get; } /// - /// The for the + /// The for the . /// protected GeneralConfiguration GeneralConfiguration { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The containing the value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The containing the value of . public WatchdogFactory( IServerControl serverControl, ILoggerFactory loggerFactory, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index 1f14c243fc..f2c22f9bc2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -45,7 +47,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IDmbProvider startupDmbProvider; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -78,7 +80,8 @@ namespace Tgstation.Server.Host.Components.Watchdog ISymlinkFactory symlinkFactory, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, - Api.Models.Instance instance, bool autoStart) + Api.Models.Instance instance, + bool autoStart) : base( chat, sessionControllerFactory, @@ -102,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch { - var _ = DisposeAsync(); + _ = DisposeAsync(); throw; } } @@ -212,9 +215,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected sealed override async Task PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) { - if(ActiveSwappable != null) + if (ActiveSwappable != null) throw new InvalidOperationException("Expected activeSwappable to be null!"); - if(startupDmbProvider != null) + if (startupDmbProvider != null) throw new InvalidOperationException("Expected startupDmbProvider to be null!"); Logger.LogTrace("Prep for server launch. pendingSwappable is {0}available", pendingSwappable == null ? "not " : String.Empty); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs index 0f7f140f6f..8d8285e06a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs @@ -1,6 +1,8 @@ +using System; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; @@ -25,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Watchdog protected ISymlinkFactory SymlinkFactory { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . diff --git a/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs index 3314c4eea5..b58747f24b 100644 --- a/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs @@ -1,24 +1,24 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Tgstation.Server.Host.Configuration { /// - /// Configuration options for the web control panel + /// Configuration options for the web control panel. /// public sealed class ControlPanelConfiguration { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "ControlPanel"; /// - /// If the control panel is enabled + /// If the control panel is enabled. /// public bool Enable { get; set; } /// - /// If any origin is allowed for CORS requests. This overrides + /// If any origin is allowed for CORS requests. This overrides . /// public bool AllowAnyOrigin { get; set; } @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Configuration public string Channel { get; set; } /// - /// Origins allowed for CORS requests + /// Origins allowed for CORS requests. /// public ICollection AllowedOrigins { get; set; } } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 88b634beae..e076bc03cb 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -4,43 +4,43 @@ using Newtonsoft.Json.Converters; namespace Tgstation.Server.Host.Configuration { /// - /// Configuration options for the + /// Configuration options for the . /// public sealed class DatabaseConfiguration { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "Database"; /// - /// The to create + /// The to create. /// [JsonConverter(typeof(StringEnumConverter))] public DatabaseType DatabaseType { get; set; } /// - /// If the admin user should be enabled and have it's password reset + /// If the admin user should be enabled and have it's password reset. /// public bool ResetAdminPassword { get; set; } /// - /// The connection string for the database + /// The connection string for the database. /// public string ConnectionString { get; set; } /// - /// If the database should be deleted on application startup. Should not be used in production! + /// If the database should be deleted on application startup. Should not be used in production!. /// public bool DropDatabase { get; set; } /// - /// Used to indicate that the database is being loaded to generate migrations. Should not be used in production! + /// Used to indicate that the database is being loaded to generate migrations. Should not be used in production!. /// public bool DesignTime { get; set; } /// - /// The form of the of the target server + /// The form of the of the target server. /// public string ServerVersion { get; set; } } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index 78c4c75362..9b3f1c89ca 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Configuration { /// - /// Type of database to user + /// Type of database to user. /// public enum DatabaseType { diff --git a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs index e8e36190cf..7dbcb2e872 100644 --- a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs @@ -1,50 +1,52 @@ -using Microsoft.Extensions.Logging; +using System; + +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using System; + using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Configuration { /// - /// File logging configuration options + /// File logging configuration options. /// public sealed class FileLoggingConfiguration { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "FileLogging"; /// - /// Default value for + /// Default value for . /// const LogLevel DefaultLogLevel = LogLevel.Debug; /// - /// Default value for + /// Default value for . /// const LogLevel DefaultMicrosoftLogLevel = LogLevel.Warning; /// - /// Where log files are stored + /// Where log files are stored. /// public string Directory { get; set; } /// - /// If file logging is disabled + /// If file logging is disabled. /// public bool Disable { get; set; } /// - /// The minimum to display in logs + /// The minimum to display in logs. /// [JsonConverter(typeof(StringEnumConverter))] public LogLevel LogLevel { get; set; } = DefaultLogLevel; /// - /// The minimum to display in logs for Microsoft library sources + /// The minimum to display in logs for Microsoft library sources. /// [JsonConverter(typeof(StringEnumConverter))] public LogLevel MicrosoftLogLevel { get; set; } = DefaultMicrosoftLogLevel; @@ -54,7 +56,7 @@ namespace Tgstation.Server.Host.Configuration /// /// The to use. /// The to use. - /// The to use + /// The to use. /// The evaluated log . public string GetFullLogDirectory( IIOManager ioManager, diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index c6ea672e48..0b2d8ae6b5 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -1,7 +1,9 @@ +using System; + using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using System; + using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Properties; using Tgstation.Server.Host.Setup; @@ -9,12 +11,12 @@ using Tgstation.Server.Host.Setup; namespace Tgstation.Server.Host.Configuration { /// - /// General configuration options + /// General configuration options. /// public sealed class GeneralConfiguration : ServerInformationBase { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "General"; @@ -23,11 +25,6 @@ namespace Tgstation.Server.Host.Configuration /// public const ushort DefaultApiPort = 5000; - /// - /// The current . - /// - public static readonly Version CurrentConfigVersion = Version.Parse(MasterVersionsAttribute.Instance.RawConfigurationVersion); - /// /// The default value for . /// @@ -49,15 +46,20 @@ namespace Tgstation.Server.Host.Configuration const uint DefaultUserGroupLimit = 25; /// - /// The default value for + /// The default value for . /// const uint DefaultByondTopicTimeout = 5000; /// - /// The default value for + /// The default value for . /// const uint DefaultRestartTimeout = 60000; + /// + /// The current . + /// + public static readonly Version CurrentConfigVersion = Version.Parse(MasterVersionsAttribute.Instance.RawConfigurationVersion); + /// /// The the file says it is. /// @@ -69,23 +71,23 @@ namespace Tgstation.Server.Host.Configuration public ushort ApiPort { get; set; } /// - /// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes + /// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes. /// public string GitHubAccessToken { get; set; } /// - /// The + /// The . /// [JsonConverter(typeof(StringEnumConverter))] public SetupWizardMode SetupWizardMode { get; set; } /// - /// The timeout in milliseconds for sending and receiving topics to/from DreamDaemon. Note that a single topic exchange can take up to twice this value + /// The timeout in milliseconds for sending and receiving topics to/from DreamDaemon. Note that a single topic exchange can take up to twice this value. /// public uint ByondTopicTimeout { get; set; } = DefaultByondTopicTimeout; /// - /// The timeout milliseconds for restarting the server + /// The timeout milliseconds for restarting the server. /// public uint RestartTimeout { get; set; } = DefaultRestartTimeout; @@ -100,7 +102,7 @@ namespace Tgstation.Server.Host.Configuration public bool HostApiDocumentation { get; set; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// public GeneralConfiguration() { diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs index b12e239911..f425e4ab36 100644 --- a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Host.Configuration { diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs index 6fb4fda5fb..9da3d398ac 100644 --- a/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs +++ b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Tgstation.Server.Host.Configuration { @@ -18,12 +18,14 @@ namespace Tgstation.Server.Host.Configuration public string ClientSecret { get; set; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - public OAuthConfigurationBase() { } + public OAuthConfigurationBase() + { + } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The to copy settings from. public OAuthConfigurationBase(OAuthConfigurationBase oAuthConfiguration) diff --git a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs index 9f6c598952..d4e8e46c15 100644 --- a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs @@ -1,15 +1,16 @@ -using System.Collections.Generic; +using System.Collections.Generic; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Configuration { /// - /// Configuration options pertaining to user security + /// Configuration options pertaining to user security. /// sealed class SecurityConfiguration { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "Security"; diff --git a/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs index 28ac063c6b..23e4b8b88d 100644 --- a/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Api.Models.Internal; using YamlDotNet.Serialization; diff --git a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs index b7cddee4d3..406560f458 100644 --- a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs @@ -1,42 +1,42 @@ namespace Tgstation.Server.Host.Configuration { /// - /// Configuration for the automatic update system + /// Configuration for the automatic update system. /// public sealed class UpdatesConfiguration { /// - /// The key for the the resides in + /// The key for the the resides in. /// public const string Section = "Updates"; /// - /// The tgstation/tgstation-server + /// The tgstation/tgstation-server . /// const long DefaultGitHubRepositoryId = 92952846; /// - /// The default value of + /// The default value of . /// const string DefaultGitTagPrefix = "tgstation-server-v"; /// - /// The default value of + /// The default value of . /// const string DefaultUpdatePackageAssetName = "ServerUpdatePackage.zip"; /// - /// The of the tgstation-server fork to recieve updates from + /// The of the tgstation-server fork to recieve updates from. /// public long GitHubRepositoryId { get; set; } = DefaultGitHubRepositoryId; /// - /// Prefix before the of TGS published in git tags + /// Prefix before the of TGS published in git tags. /// public string GitTagPrefix { get; set; } = DefaultGitTagPrefix; /// - /// Asset package containing the new assembly in zip form + /// Asset package containing the new assembly in zip form. /// public string UpdatePackageAssetName { get; set; } = DefaultUpdatePackageAssetName; } diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 6fa5369700..6b653b2eed 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -1,14 +1,16 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Octokit; -using System; +using System; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Web; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Octokit; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -26,40 +28,43 @@ using Tgstation.Server.Host.Transfer; namespace Tgstation.Server.Host.Controllers { /// - /// for purposes + /// for TGS administration purposes. /// [Route(Routes.Administration)] public sealed class AdministrationController : ApiController { + /// + /// Default for s. + /// const string OctokitException = "Bad GitHub API response, check configuration!"; /// - /// The for the + /// The for the . /// readonly IGitHubClientFactory gitHubClientFactory; /// - /// The for the + /// The for the . /// readonly IServerControl serverControl; /// - /// The for the + /// The for the . /// readonly IServerUpdateInitiator serverUpdater; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; @@ -69,30 +74,30 @@ namespace Tgstation.Server.Host.Controllers readonly IFileTransferTicketProvider fileTransferService; /// - /// The for the + /// The for the . /// readonly UpdatesConfiguration updatesConfiguration; /// - /// The for the + /// The for the . /// readonly FileLoggingConfiguration fileLoggingConfiguration; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of + /// The for the . + /// The for the . + /// The value of . /// The value of . /// The value of . - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . /// The value of . - /// The for the - /// The containing value of - /// The containing value of + /// The for the . + /// The containing value of . + /// The containing value of . public AdministrationController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -159,8 +164,8 @@ namespace Tgstation.Server.Host.Controllers updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); - foreach (var I in releases) - if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) + foreach (var release in releases) + if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version.Major == assemblyInformationProvider.Version.Major && (greatestVersion == null || version > greatestVersion)) greatestVersion = version; @@ -186,7 +191,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogWarning(e, OctokitException); return StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) { - AdditionalData = e.Message + AdditionalData = e.Message, }); } } @@ -217,7 +222,7 @@ namespace Tgstation.Server.Host.Controllers if (model.NewVersion == null) return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure) { - AdditionalData = "newVersion is required!" + AdditionalData = "newVersion is required!", }); if (model.NewVersion.Major != assemblyInformationProvider.Version.Major) @@ -237,7 +242,7 @@ namespace Tgstation.Server.Host.Controllers return Accepted(new ServerUpdateResponse { - NewVersion = model.NewVersion + NewVersion = model.NewVersion, }); } catch (RateLimitExceededException e) @@ -249,7 +254,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogWarning(e, OctokitException); return StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) { - AdditionalData = e.Message + AdditionalData = e.Message, }); } } @@ -257,7 +262,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Attempts to restart the server. /// - /// A resulting in the of the request + /// A resulting in the of the request. /// Restart begun successfully. /// Restart operations are unavailable due to the launch configuration of TGS. [HttpDelete] @@ -312,7 +317,7 @@ namespace Tgstation.Server.Host.Controllers .GetLastModified( ioManager.ConcatPath(path, file), cancellationToken) - .ConfigureAwait(false) + .ConfigureAwait(false), }) .ToList(); @@ -329,7 +334,7 @@ namespace Tgstation.Server.Host.Controllers return new PaginatableResult( Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = ex.ToString() + AdditionalData = ex.ToString(), })); } }, @@ -380,14 +385,14 @@ namespace Tgstation.Server.Host.Controllers { Name = path, LastModified = await ioManager.GetLastModified(fullPath, cancellationToken).ConfigureAwait(false), - FileTicket = fileTransferTicket.FileTicket + FileTicket = fileTransferTicket.FileTicket, }); } catch (IOException ex) { return Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = ex.ToString() + AdditionalData = ex.ToString(), }); } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 659f7da2e8..16cda75523 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -1,3 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -7,14 +16,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using Octokit; using Serilog.Context; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Net.Mime; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -25,7 +27,7 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// A for API functions + /// Base for API functions. /// [Produces(MediaTypeNames.Application.Json)] [ApiController] @@ -42,42 +44,42 @@ namespace Tgstation.Server.Host.Controllers private const ushort MaximumPageSize = 100; /// - /// The for the operation + /// The for the operation. /// protected ApiHeaders ApiHeaders { get; private set; } /// - /// The for the operation + /// The for the operation. /// protected IDatabaseContext DatabaseContext { get; } /// - /// The for the operation + /// The for the operation. /// protected IAuthenticationContext AuthenticationContext { get; } /// - /// The for the + /// The for the . /// protected ILogger Logger { get; } /// - /// The for the operation + /// The for the operation. /// protected Models.Instance Instance { get; } /// - /// If are required + /// If are required. /// readonly bool requireHeaders; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The for the - /// The value of - /// The value of + /// The value of . + /// The for the . + /// The value of . + /// The value of . protected ApiController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -93,6 +95,109 @@ namespace Tgstation.Server.Host.Controllers this.requireHeaders = requireHeaders; } + /// +#pragma warning disable CA1506 // TODO: Decomplexify + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + // ALL valid token and login requests that match a route go through this function + // 404 is returned before + if (AuthenticationContext != null && AuthenticationContext.User == null) + { + // valid token, expired password + await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + // validate the headers + try + { + ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); + + if (!ApiHeaders.Compatible()) + { + await StatusCode( + HttpStatusCode.UpgradeRequired, + new ErrorMessageResponse(ErrorCode.ApiMismatch)) + .ExecuteResultAsync(context) + .ConfigureAwait(false); + return; + } + + var errorCase = await ValidateRequest(context.HttpContext.RequestAborted).ConfigureAwait(false); + if (errorCase != null) + { + await errorCase.ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + } + catch (HeadersException) + { + if (requireHeaders) + { + await HeadersIssue(false) + .ExecuteResultAsync(context) + .ConfigureAwait(false); + return; + } + } + + if (ModelState?.IsValid == false) + { + var errorMessages = ModelState + .SelectMany(x => x.Value.Errors) + .Select(x => x.ErrorMessage) + + // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases + // We validate missing required fields in controllers + // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength + // This is the best way to deal with it unfortunately + .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); + + if (errorMessages.Any()) + { + await BadRequest( + new ErrorMessageResponse(ErrorCode.ModelValidationFailure) + { + AdditionalData = String.Join(Environment.NewLine, errorMessages), + }) + .ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + ModelState.Clear(); + } + + using (ApiHeaders?.InstanceId != null + ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) + : null) + using (AuthenticationContext != null + ? LogContext.PushProperty("User", AuthenticationContext.User.Id) + : null) + using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) + { + if (ApiHeaders != null) + Logger.LogDebug( + "Starting API request: Version: {0}. {1}: {2}", + ApiHeaders.ApiVersion.Semver(), + HeaderNames.UserAgent, + ApiHeaders.RawUserAgent); + else if (Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgents)) + Logger.LogDebug( + "Starting unauthorized API request. {0}: {1}", + HeaderNames.UserAgent, + userAgents); + else + Logger.LogDebug( + "Starting unauthorized API request. No {0}!", + HeaderNames.UserAgent); + await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); + } + } +#pragma warning restore CA1506 + /// /// Generic 410 response. /// @@ -167,7 +272,7 @@ namespace Tgstation.Server.Host.Controllers HeadersException headersException; try { - var _ = new ApiHeaders(Request.GetTypedHeaders(), ignoreMissingAuth); + _ = new ApiHeaders(Request.GetTypedHeaders(), ignoreMissingAuth); throw new InvalidOperationException("Expected a header parse exception!"); } catch (HeadersException ex) @@ -177,7 +282,7 @@ namespace Tgstation.Server.Host.Controllers var errorMessage = new ErrorMessageResponse(ErrorCode.BadHeaders) { - AdditionalData = headersException.Message + AdditionalData = headersException.Message, }; if (headersException.MissingOrMalformedHeaders.HasFlag(HeaderTypes.Accept)) @@ -186,109 +291,6 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(errorMessage); } - /// -#pragma warning disable CA1506 // TODO: Decomplexify - public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - // ALL valid token and login requests that match a route go through this function - // 404 is returned before - if (AuthenticationContext != null && AuthenticationContext.User == null) - { - // valid token, expired password - await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - - // validate the headers - try - { - ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); - - if (!ApiHeaders.Compatible()) - { - await StatusCode( - HttpStatusCode.UpgradeRequired, - new ErrorMessageResponse(ErrorCode.ApiMismatch)) - .ExecuteResultAsync(context) - .ConfigureAwait(false); - return; - } - - var errorCase = await ValidateRequest(context.HttpContext.RequestAborted).ConfigureAwait(false); - if (errorCase != null) - { - await errorCase.ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - } - catch (HeadersException) - { - if (requireHeaders) - { - await HeadersIssue(false) - .ExecuteResultAsync(context) - .ConfigureAwait(false); - return; - } - } - - if (ModelState?.IsValid == false) - { - var errorMessages = ModelState - .SelectMany(x => x.Value.Errors) - .Select(x => x.ErrorMessage) - - // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases - // We validate missing required fields in controllers - // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength - // This is the best way to deal with it unfortunately - .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); - - if (errorMessages.Any()) - { - await BadRequest( - new ErrorMessageResponse(ErrorCode.ModelValidationFailure) - { - AdditionalData = String.Join(Environment.NewLine, errorMessages) - }) - .ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - - ModelState.Clear(); - } - - using (ApiHeaders?.InstanceId != null - ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) - : null) - using (AuthenticationContext != null - ? LogContext.PushProperty("User", AuthenticationContext.User.Id) - : null) - using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) - { - if (ApiHeaders != null) - Logger.LogDebug( - "Starting API request: Version: {0}. {1}: {2}", - ApiHeaders.ApiVersion.Semver(), - HeaderNames.UserAgent, - ApiHeaders.RawUserAgent); - else if (Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgents)) - Logger.LogDebug( - "Starting unauthorized API request. {0}: {1}", - HeaderNames.UserAgent, - userAgents); - else - Logger.LogDebug( - "Starting unauthorized API request. No {0}!", - HeaderNames.UserAgent); - await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); - } - } -#pragma warning restore CA1506 - /// /// Generates a paginated response. /// @@ -364,7 +366,7 @@ namespace Tgstation.Server.Host.Controllers if (pageSize > MaximumPageSize) return BadRequest(new ErrorMessageResponse(ErrorCode.ApiPageTooLarge) { - AdditionalData = $"Maximum page size: {MaximumPageSize}" + AdditionalData = $"Maximum page size: {MaximumPageSize}", }); var page = pageQuery ?? 1; @@ -403,15 +405,15 @@ namespace Tgstation.Server.Host.Controllers .ToList(); if (resultTransformer != null) - foreach (var I in finalResults) - resultTransformer(I); + foreach (var finalResult in finalResults) + resultTransformer(finalResult); return Json( new PaginatedResponse { Content = finalResults, PageSize = pageSize, - TotalPages = (ushort)((totalResults / pageSize) + 1) + TotalPages = (ushort)((totalResults / pageSize) + 1), }); } } diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs index d9397874b4..9d7afa691c 100644 --- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs +++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs @@ -1,13 +1,15 @@ +using System; +using System.Net; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Serilog.Context; -using System; -using System.Net; -using System.Net.Mime; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; @@ -28,17 +30,17 @@ namespace Tgstation.Server.Host.Controllers static long requestsProcessed; /// - /// The for the + /// The for the . /// readonly IBridgeDispatcher bridgeDispatcher; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The of the server. @@ -58,10 +60,10 @@ namespace Tgstation.Server.Host.Controllers /// Processes a bridge request. /// /// JSON encoded . - /// The for the operation + /// The for the operation. /// A resulting in the for the operation. [HttpGet] - public async Task Process([FromQuery]string data, CancellationToken cancellationToken) + public async Task Process([FromQuery] string data, CancellationToken cancellationToken) { // Nothing to see here var remoteIP = Request.HttpContext.Connection.RemoteIpAddress; diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 26d14a7033..e313991fb3 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; +using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -26,7 +28,7 @@ namespace Tgstation.Server.Host.Controllers public sealed class ByondController : InstanceRequiredController { /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; @@ -36,14 +38,14 @@ namespace Tgstation.Server.Host.Controllers readonly IFileTransferTicketProvider fileTransferService; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The for the . - /// The value of + /// The value of . /// The value of . - /// The for the + /// The for the . public ByondController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -74,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers Task.FromResult( Json(new ByondResponse { - Version = instance.ByondManager.ActiveVersion + Version = instance.ByondManager.ActiveVersion, }))); /// @@ -98,7 +100,7 @@ namespace Tgstation.Server.Host.Controllers .InstalledVersions .Select(x => new ByondResponse { - Version = x + Version = x, }) .AsQueryable() .OrderBy(x => x.Version))), @@ -174,7 +176,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Byond, CancelRight = (ulong)ByondRights.CancelInstall, - Instance = Instance + Instance = Instance, }; IFileUploadTicket fileUploadTicket = null; diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index d349de7b13..75a1e52459 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -6,9 +6,11 @@ using System.Net; using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; @@ -19,24 +21,25 @@ using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; + using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Controllers { /// - /// for managing s + /// for managing s. /// [Route(Routes.Chat)] - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public sealed class ChatController : InstanceRequiredController { /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The for the . - /// The for the + /// The for the . public ChatController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -51,10 +54,10 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Converts to a + /// Converts to a . /// - /// The - /// A based on + /// The . + /// A based on . static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api) => new Models.ChatChannel { DiscordChannelId = api.DiscordChannelId, @@ -62,7 +65,7 @@ namespace Tgstation.Server.Host.Controllers IsAdminChannel = api.IsAdminChannel ?? false, IsWatchdogChannel = api.IsWatchdogChannel ?? false, IsUpdatesChannel = api.IsUpdatesChannel ?? false, - Tag = api.Tag + Tag = api.Tag, }; /// @@ -107,7 +110,7 @@ namespace Tgstation.Server.Host.Controllers InstanceId = Instance.Id.Value, Provider = model.Provider, ReconnectionInterval = model.ReconnectionInterval, - ChannelLimit = model.ChannelLimit + ChannelLimit = model.ChannelLimit, }; DatabaseContext.ChatBots.Add(dbModel); @@ -246,9 +249,9 @@ namespace Tgstation.Server.Host.Controllers [ProducesResponseType(typeof(ChatBotResponse), 200)] [ProducesResponseType(204)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] - #pragma warning disable CA1502, CA1506 // TODO: Decomplexify +#pragma warning disable CA1502, CA1506 // TODO: Decomplexify public async Task Update([FromBody] ChatBotUpdateRequest model, CancellationToken cancellationToken) - #pragma warning restore CA1502, CA1506 +#pragma warning restore CA1502, CA1506 { if (model == null) throw new ArgumentNullException(nameof(model)); @@ -383,5 +386,5 @@ namespace Tgstation.Server.Host.Controllers return null; } } - #pragma warning restore CA1506 +#pragma warning restore CA1506 } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 8dd9456219..eaa3605726 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; +using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -19,24 +21,24 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// The for s + /// The for s. /// [Route(Routes.Configuration)] public sealed class ConfigurationController : InstanceRequiredController { /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The for the . - /// The value of - /// The for the + /// The value of . + /// The for the . public ConfigurationController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -52,26 +54,6 @@ namespace Tgstation.Server.Host.Controllers this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); } - /// - /// If a should be returned from actions due to conflicts with one or both of the or the or a given tries to access parent directories - /// - /// The path to validate if any - /// The to use when calling into - /// if a should be returned, otherwise - bool ForbidDueToModeConflicts(string path, out ISystemIdentity systemIdentityToUse) - { - if (Instance.ConfigurationType == ConfigurationType.Disallowed - || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null) - || (path != null && ioManager.PathContainsParentAccess(path))) - { - systemIdentityToUse = null; - return true; - } - - systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null; - return false; - } - /// /// Write to a configuration file. /// @@ -109,12 +91,12 @@ namespace Tgstation.Server.Host.Controllers }) .ConfigureAwait(false); } - catch(IOException e) + catch (IOException e) { Logger.LogInformation("IOException while updating file {0}: {1}", model.Path, e); return Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = e.Message + AdditionalData = e.Message, }); } catch (NotImplementedException) @@ -124,11 +106,11 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Get the contents of a file at a + /// Get the contents of a file at a . /// - /// The path of the file to get - /// The for the operation - /// A resulting in the for the operation> + /// The path of the file to get. + /// The for the operation. + /// A resulting in the for the operation.> /// File read successfully.> /// File does not currently exist. [HttpGet(Routes.File + "/{*filePath}")] @@ -161,7 +143,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogInformation("IOException while reading file {0}: {1}", filePath, e); return Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = e.Message + AdditionalData = e.Message, }); } catch (NotImplementedException) @@ -171,13 +153,13 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Get the contents of a directory at a + /// Get the contents of a directory at a . /// - /// The path of the directory to get + /// The path of the directory to get. /// The current page. /// The page size. - /// The for the operation - /// A resulting in the for the operation + /// The for the operation. + /// A resulting in the for the operation. /// Directory listed successfully.> /// Directory does not currently exist. [HttpGet(Routes.List + "/{*directoryPath}")] @@ -267,7 +249,7 @@ namespace Tgstation.Server.Host.Controllers var resultModel = new ConfigurationFileResponse { IsDirectory = true, - Path = model.Path + Path = model.Path, }; return await WithComponentInstance( @@ -284,7 +266,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogInformation("IOException while creating directory {0}: {1}", model.Path, e); return Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - Message = e.Message + Message = e.Message, }); } catch (NotImplementedException) @@ -298,11 +280,11 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Deletes an empty + /// Deletes an empty . /// - /// A representing the path to the directory to delete - /// The for the operation - /// A resulting in the of the operation + /// A representing the path to the directory to delete. + /// The for the operation. + /// A resulting in the of the operation. /// Empty directory deleted successfully. [HttpDelete] [TgsAuthorize(ConfigurationRights.Delete)] @@ -338,5 +320,25 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); } } + + /// + /// If a should be returned from actions due to conflicts with one or both of the or the or a given tries to access parent directories. + /// + /// The path to validate if any. + /// The to use when calling into . + /// if a should be returned, otherwise. + bool ForbidDueToModeConflicts(string path, out ISystemIdentity systemIdentityToUse) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed + || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null) + || (path != null && ioManager.PathContainsParentAccess(path))) + { + systemIdentityToUse = null; + return true; + } + + systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null; + return false; + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs index cd3f70a75f..e9373c9d43 100644 --- a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs @@ -1,12 +1,14 @@ +using System; +using System.Globalization; +using System.Net.Mime; + using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using System; -using System.Globalization; -using System.Net.Mime; + using Tgstation.Server.Api; using Tgstation.Server.Host.Configuration; @@ -35,7 +37,7 @@ namespace Tgstation.Server.Host.Controllers readonly ControlPanelConfiguration controlPanelConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The containing the value of . diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index dbbf353758..e1e97ea0b4 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -1,12 +1,14 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -22,7 +24,7 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// for managing the + /// for managing the . /// [Route(Routes.DreamDaemon)] public sealed class DreamDaemonController : InstanceRequiredController @@ -38,14 +40,14 @@ namespace Tgstation.Server.Host.Controllers readonly IPortAllocator portAllocator; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of + /// The for the . + /// The for the . + /// The value of . /// The for the . /// The value of . - /// The for the + /// The for the . public DreamDaemonController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -84,7 +86,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (ulong)DreamDaemonRights.Shutdown, CancelRightsType = RightsType.DreamDaemon, Instance = Instance, - StartedBy = AuthenticationContext.User + StartedBy = AuthenticationContext.User, }; await jobManager.RegisterOperation( job, @@ -107,69 +109,6 @@ namespace Tgstation.Server.Host.Controllers [ProducesResponseType(typeof(ErrorMessageResponse), 410)] public Task Read(CancellationToken cancellationToken) => ReadImpl(null, cancellationToken); - /// - /// Implementation of - /// - /// The to operate on if any - /// The for the operation - /// A resulting in the of the operation - Task ReadImpl(DreamDaemonSettings settings, CancellationToken cancellationToken) - => WithComponentInstance(async instance => - { - var dd = instance.Watchdog; - - var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0; - var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0; - - if (settings == null) - { - settings = await DatabaseContext - .Instances - .AsQueryable() - .Where(x => x.Id == Instance.Id) - .Select(x => x.DreamDaemonSettings) - .FirstOrDefaultAsync(cancellationToken) - .ConfigureAwait(false); - if (settings == default) - return Gone(); - } - - var result = new DreamDaemonResponse(); - if (metadata) - { - var alphaActive = dd.AlphaIsActive; - var llp = dd.LastLaunchParameters; - var rstate = dd.RebootState; - result.AutoStart = settings.AutoStart.Value; - result.CurrentPort = llp?.Port.Value; - result.CurrentSecurity = llp?.SecurityLevel.Value; - result.CurrentAllowWebclient = llp?.AllowWebClient.Value; - result.Port = settings.Port.Value; - result.AllowWebClient = settings.AllowWebClient.Value; - result.Status = dd.Status; - result.SecurityLevel = settings.SecurityLevel.Value; - result.SoftRestart = rstate == RebootState.Restart; - result.SoftShutdown = rstate == RebootState.Shutdown; - result.StartupTimeout = settings.StartupTimeout.Value; - result.HeartbeatSeconds = settings.HeartbeatSeconds.Value; - result.TopicRequestTimeout = settings.TopicRequestTimeout.Value; - result.AdditionalParameters = settings.AdditionalParameters; - } - - if (revision) - { - var latestCompileJob = instance.LatestCompileJob(); - result.ActiveCompileJob = ((instance.Watchdog.Status != WatchdogStatus.Offline - ? dd.ActiveCompileJob - : latestCompileJob) ?? latestCompileJob) - ?.ToApi(); - if (latestCompileJob?.Id != result.ActiveCompileJob?.Id) - result.StagedCompileJob = latestCompileJob?.ToApi(); - } - - return Json(result); - }); - /// /// Stops the Watchdog if it's running. /// @@ -208,8 +147,8 @@ namespace Tgstation.Server.Host.Controllers | DreamDaemonRights.SetTopicTimeout)] [ProducesResponseType(typeof(DreamDaemonResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] - #pragma warning disable CA1502 // TODO: Decomplexify - #pragma warning disable CA1506 +#pragma warning disable CA1502 // TODO: Decomplexify +#pragma warning disable CA1506 public async Task Update([FromBody] DreamDaemonResponse model, CancellationToken cancellationToken) { if (model == null) @@ -301,8 +240,8 @@ namespace Tgstation.Server.Host.Controllers /// /// Creates a to restart the Watchdog. It will not start if it wasn't already running. /// - /// The for the operation - /// A resulting in the of the request + /// The for the operation. + /// A resulting in the of the request. /// Restart started successfully. [HttpPatch] [TgsAuthorize(DreamDaemonRights.Restart)] @@ -316,7 +255,7 @@ namespace Tgstation.Server.Host.Controllers CancelRightsType = RightsType.DreamDaemon, CancelRight = (ulong)DreamDaemonRights.Shutdown, StartedBy = AuthenticationContext.User, - Description = "Restart Watchdog" + Description = "Restart Watchdog", }; var watchdog = instance.Watchdog; @@ -335,8 +274,8 @@ namespace Tgstation.Server.Host.Controllers /// /// Creates a to generate a DreamDaemon process dump. /// - /// The for the operation - /// A resulting in the of the request + /// The for the operation. + /// A resulting in the of the request. /// Dump started successfully. [HttpPatch(Routes.Diagnostics)] [TgsAuthorize(DreamDaemonRights.CreateDump)] @@ -350,7 +289,7 @@ namespace Tgstation.Server.Host.Controllers CancelRightsType = RightsType.DreamDaemon, CancelRight = (ulong)DreamDaemonRights.CreateDump, StartedBy = AuthenticationContext.User, - Description = "Create DreamDaemon Process Dump" + Description = "Create DreamDaemon Process Dump", }; var watchdog = instance.Watchdog; @@ -360,9 +299,73 @@ namespace Tgstation.Server.Host.Controllers await jobManager.RegisterOperation( job, - (core, databaseContextFactory, paramJob, progressReporter, ct) => core.Watchdog.CreateDump(ct), cancellationToken) + (core, databaseContextFactory, paramJob, progressReporter, ct) => core.Watchdog.CreateDump(ct), + cancellationToken) .ConfigureAwait(false); return Accepted(job.ToApi()); }); + + /// + /// Implementation of . + /// + /// The to operate on if any. + /// The for the operation. + /// A resulting in the of the operation. + Task ReadImpl(DreamDaemonSettings settings, CancellationToken cancellationToken) + => WithComponentInstance(async instance => + { + var dd = instance.Watchdog; + + var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0; + var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0; + + if (settings == null) + { + settings = await DatabaseContext + .Instances + .AsQueryable() + .Where(x => x.Id == Instance.Id) + .Select(x => x.DreamDaemonSettings) + .FirstOrDefaultAsync(cancellationToken) + .ConfigureAwait(false); + if (settings == default) + return Gone(); + } + + var result = new DreamDaemonResponse(); + if (metadata) + { + var alphaActive = dd.AlphaIsActive; + var llp = dd.LastLaunchParameters; + var rstate = dd.RebootState; + result.AutoStart = settings.AutoStart.Value; + result.CurrentPort = llp?.Port.Value; + result.CurrentSecurity = llp?.SecurityLevel.Value; + result.CurrentAllowWebclient = llp?.AllowWebClient.Value; + result.Port = settings.Port.Value; + result.AllowWebClient = settings.AllowWebClient.Value; + result.Status = dd.Status; + result.SecurityLevel = settings.SecurityLevel.Value; + result.SoftRestart = rstate == RebootState.Restart; + result.SoftShutdown = rstate == RebootState.Shutdown; + result.StartupTimeout = settings.StartupTimeout.Value; + result.HeartbeatSeconds = settings.HeartbeatSeconds.Value; + result.TopicRequestTimeout = settings.TopicRequestTimeout.Value; + result.AdditionalParameters = settings.AdditionalParameters; + } + + if (revision) + { + var latestCompileJob = instance.LatestCompileJob(); + result.ActiveCompileJob = ((instance.Watchdog.Status != WatchdogStatus.Offline + ? dd.ActiveCompileJob + : latestCompileJob) ?? latestCompileJob) + ?.ToApi(); + if (latestCompileJob?.Id != result.ActiveCompileJob?.Id) + result.StagedCompileJob = latestCompileJob?.ToApi(); + } + + return Json(result); + }); } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index e142c3f0ed..40bd70edb4 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -23,11 +25,11 @@ namespace Tgstation.Server.Host.Controllers /// for managing the deployment system. /// [Route(Routes.DreamMaker)] - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public sealed class DreamMakerController : InstanceRequiredController { /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; @@ -37,14 +39,14 @@ namespace Tgstation.Server.Host.Controllers readonly IPortAllocator portAllocator; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of + /// The for the . + /// The for the . + /// The value of . /// The for the . /// The value of . - /// The for the + /// The for the . public DreamMakerController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -104,24 +106,6 @@ namespace Tgstation.Server.Host.Controllers return Json(compileJob.ToApi()); } - /// - /// Base query for pulling in all required fields. - /// - /// An of with all the inclusions. - IQueryable BaseCompileJobsQuery() => DatabaseContext - .CompileJobs - .AsQueryable() - .Include(x => x.Job) - .ThenInclude(x => x.StartedBy) - .Include(x => x.RevisionInformation) - .ThenInclude(x => x.PrimaryTestMerge) - .ThenInclude(x => x.MergedBy) - .Include(x => x.RevisionInformation) - .ThenInclude(x => x.ActiveTestMerges) - .ThenInclude(x => x.TestMerge) - .ThenInclude(x => x.MergedBy) - .Where(x => x.Job.Instance.Id == Instance.Id); - /// /// List all s for the instance. /// @@ -161,7 +145,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, CancelRight = (ulong)DreamMakerRights.CancelCompile, - Instance = Instance + Instance = Instance, }; await jobManager.RegisterOperation( @@ -259,5 +243,23 @@ namespace Tgstation.Server.Host.Controllers return await Read(cancellationToken).ConfigureAwait(false); } + + /// + /// Base query for pulling in all required fields. + /// + /// An of with all the inclusions. + IQueryable BaseCompileJobsQuery() => DatabaseContext + .CompileJobs + .AsQueryable() + .Include(x => x.Job) + .ThenInclude(x => x.StartedBy) + .Include(x => x.RevisionInformation) + .ThenInclude(x => x.PrimaryTestMerge) + .ThenInclude(x => x.MergedBy) + .Include(x => x.RevisionInformation) + .ThenInclude(x => x.ActiveTestMerges) + .ThenInclude(x => x.TestMerge) + .ThenInclude(x => x.MergedBy) + .Where(x => x.Job.Instance.Id == Instance.Id); } } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index a402527a00..883c714ffd 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,3 +1,9 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -7,11 +13,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Octokit; -using System; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -28,33 +30,33 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Controllers { /// - /// Main for the + /// Root for the . /// [Route(Routes.Root)] public sealed class HomeController : ApiController { /// - /// The for the + /// The for the . /// readonly ITokenFactory tokenFactory; /// - /// The for the + /// The for the . /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the . /// readonly ICryptographySuite cryptographySuite; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IIdentityCache identityCache; @@ -84,27 +86,27 @@ namespace Tgstation.Server.Host.Controllers readonly GeneralConfiguration generalConfiguration; /// - /// The for the + /// The for the . /// readonly ControlPanelConfiguration controlPanelConfiguration; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The for the . + /// The for the . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . /// The containing the value of . - /// The containing the value of - /// The for the + /// The containing the value of . + /// The for the . public HomeController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -140,7 +142,7 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Main page of the + /// Main page of the . /// /// The for the operation. /// @@ -156,10 +158,7 @@ namespace Tgstation.Server.Host.Controllers if (controlPanelConfiguration.Enable) Response.Headers.Add( HeaderNames.Vary, - new StringValues( - new[]{ - ApiHeaders.ApiVersionHeader - })); + new StringValues(ApiHeaders.ApiVersionHeader)); if (ApiHeaders == null) { @@ -200,13 +199,13 @@ namespace Tgstation.Server.Host.Controllers UpdateInProgress = serverControl.UpdateInProgress, }); } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// - /// Attempt to authenticate a using + /// Attempt to authenticate a using . /// - /// The for the operation - /// A resulting in the of the operation + /// The for the operation. + /// A resulting in the of the operation. /// User logged in and generated successfully. /// User authentication failed. /// User authenticated but is disabled by an administrator. @@ -260,7 +259,7 @@ namespace Tgstation.Server.Host.Controllers .ValidateResponseCode(ApiHeaders.Token, cancellationToken) .ConfigureAwait(false); - Logger.LogTrace("External {0} UID: {1}", oAuthProvider, externalUserId); + Logger.LogTrace("External {0} UID: {1}", oAuthProvider, externalUserId); } catch (RateLimitExceededException ex) { @@ -290,7 +289,7 @@ namespace Tgstation.Server.Host.Controllers Id = x.Id, PasswordHash = x.PasswordHash, Enabled = x.Enabled, - Name = x.Name + Name = x.Name, }) .ToListAsync(cancellationToken) .ConfigureAwait(false); @@ -323,7 +322,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id); var updatedUser = new Models.User { - Id = user.Id + Id = user.Id, }; DatabaseContext.Users.Attach(updatedUser); updatedUser.PasswordHash = user.PasswordHash; diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index b13aaa2c3b..6211183321 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -1,8 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,6 +6,12 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -28,10 +30,10 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Controllers { /// - /// for managing s + /// for managing s. /// [Route(Routes.InstanceManager)] - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public sealed class InstanceController : ApiController { /// @@ -45,22 +47,22 @@ namespace Tgstation.Server.Host.Controllers const string MoveInstanceJobPrefix = "Move instance ID "; /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// The for the + /// The for the . /// readonly IInstanceManager instanceManager; /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; @@ -80,14 +82,14 @@ namespace Tgstation.Server.Host.Controllers readonly SwarmConfiguration swarmConfiguration; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of - /// The value of - /// The value of - /// The value of + /// The for the . + /// The for the . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The containing the value of . /// The containing the value of . @@ -118,115 +120,6 @@ namespace Tgstation.Server.Host.Controllers swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions)); } - /// - /// Creates a default from . - /// - /// The . - /// The for the operation. - /// A resulting in the new or if ports could not be allocated. - async Task CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken) - { - var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken).ConfigureAwait(false); - if (!ddPort.HasValue) - return null; - - // try to use the old default if possible - const ushort DefaultDreamDaemonPort = 1337; - if (ddPort.Value < DefaultDreamDaemonPort) - ddPort = await portAllocator.GetAvailablePort(DefaultDreamDaemonPort, false, cancellationToken).ConfigureAwait(false) ?? ddPort; - - const ushort DefaultApiValidationPort = 1339; - var dmPort = await portAllocator - .GetAvailablePort( - Math.Min((ushort)(ddPort.Value + 1), DefaultApiValidationPort), - false, - cancellationToken) - .ConfigureAwait(false); - if (!dmPort.HasValue) - return null; - - // try to use the old default if possible - if (dmPort < DefaultApiValidationPort) - dmPort = await portAllocator.GetAvailablePort(DefaultApiValidationPort, false, cancellationToken).ConfigureAwait(false) ?? dmPort; - - return new Models.Instance - { - ConfigurationType = initialSettings.ConfigurationType ?? ConfigurationType.Disallowed, - DreamDaemonSettings = new DreamDaemonSettings - { - AllowWebClient = false, - AutoStart = false, - Port = ddPort, - SecurityLevel = DreamDaemonSecurity.Safe, - StartupTimeout = 60, - HeartbeatSeconds = 60, - TopicRequestTimeout = generalConfiguration.ByondTopicTimeout, - AdditionalParameters = String.Empty, - }, - DreamMakerSettings = new DreamMakerSettings - { - ApiValidationPort = dmPort, - ApiValidationSecurityLevel = DreamDaemonSecurity.Safe, - RequireDMApiValidation = true - }, - Name = initialSettings.Name, - Online = false, - Path = initialSettings.Path, - AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0, - ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit, - RepositorySettings = new RepositorySettings - { - CommitterEmail = Components.Repository.Repository.DefaultCommitterEmail, - CommitterName = Components.Repository.Repository.DefaultCommitterName, - PushTestMergeCommits = false, - ShowTestMergeCommitters = false, - AutoUpdatesKeepTestMerges = false, - AutoUpdatesSynchronize = false, - PostTestMergeComment = false, - CreateGitHubDeployments = false - }, - InstancePermissionSets = new List // give this user full privileges on the instance - { - InstanceAdminPermissionSet(null) - }, - SwarmIdentifer = swarmConfiguration.Identifier, - }; - } - - string NormalizePath(string path) - { - if (path == null) - return null; - - path = ioManager.ResolvePath(path); - if (platformIdentifier.IsWindows) - path = path.ToUpperInvariant().Replace('\\', '/'); - - return path; - } - - /// - /// Generate an with full rights. - /// - /// An optional existing to update. - /// or a new with full rights. - InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet permissionSetToModify) - { - if (permissionSetToModify == null) - permissionSetToModify = new InstancePermissionSet() - { - PermissionSetId = AuthenticationContext.PermissionSet.Id.Value - }; - permissionSetToModify.ByondRights = RightsHelper.AllRights(); - permissionSetToModify.ChatBotRights = RightsHelper.AllRights(); - permissionSetToModify.ConfigurationRights = RightsHelper.AllRights(); - permissionSetToModify.DreamDaemonRights = RightsHelper.AllRights(); - permissionSetToModify.DreamMakerRights = RightsHelper.AllRights(); - permissionSetToModify.RepositoryRights = RightsHelper.AllRights(); - permissionSetToModify.InstancePermissionSetRights = RightsHelper.AllRights(); - return permissionSetToModify; - } - /// /// Create or attach an . /// @@ -282,7 +175,7 @@ namespace Tgstation.Server.Host.Controllers .Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier) .Select(x => new Models.Instance { - Path = x.Path + Path = x.Path, }) .ForEachAsync( otherInstance => @@ -364,7 +257,7 @@ namespace Tgstation.Server.Host.Controllers { return Conflict(new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = e.Message + AdditionalData = e.Message, }); } @@ -448,7 +341,7 @@ namespace Tgstation.Server.Host.Controllers #pragma warning restore CA1310 // Specify StringComparison .Select(x => new Job { - Id = x.Id + Id = x.Id, }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (moveJob != default) @@ -557,7 +450,7 @@ namespace Tgstation.Server.Host.Controllers } catch (Exception e) { - if(!(e is OperationCanceledException)) + if (!(e is OperationCanceledException)) Logger.LogError(e, "Error changing instance online state!"); originalModel.Online = originalOnline; originalModel.DreamDaemonSettings.AutoStart = oldAutoStart; @@ -571,7 +464,7 @@ namespace Tgstation.Server.Host.Controllers var api = (AuthenticationContext.GetRight(RightsType.InstanceManager) & (ulong)InstanceManagerRights.Read) != 0 ? originalModel.ToApi() : new InstanceResponse { - Id = originalModel.Id + Id = originalModel.Id, }; var moving = originalModelPath != null; @@ -583,7 +476,7 @@ namespace Tgstation.Server.Host.Controllers Instance = originalModel, CancelRightsType = RightsType.InstanceManager, CancelRight = (ulong)InstanceManagerRights.Relocate, - StartedBy = AuthenticationContext.User + StartedBy = AuthenticationContext.User, }; await jobManager.RegisterOperation( @@ -778,5 +671,119 @@ namespace Tgstation.Server.Host.Controllers return NoContent(); } + + /// + /// Creates a default from . + /// + /// The . + /// The for the operation. + /// A resulting in the new or if ports could not be allocated. + async Task CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken) + { + var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken).ConfigureAwait(false); + if (!ddPort.HasValue) + return null; + + // try to use the old default if possible + const ushort DefaultDreamDaemonPort = 1337; + if (ddPort.Value < DefaultDreamDaemonPort) + ddPort = await portAllocator.GetAvailablePort(DefaultDreamDaemonPort, false, cancellationToken).ConfigureAwait(false) ?? ddPort; + + const ushort DefaultApiValidationPort = 1339; + var dmPort = await portAllocator + .GetAvailablePort( + Math.Min((ushort)(ddPort.Value + 1), DefaultApiValidationPort), + false, + cancellationToken) + .ConfigureAwait(false); + if (!dmPort.HasValue) + return null; + + // try to use the old default if possible + if (dmPort < DefaultApiValidationPort) + dmPort = await portAllocator.GetAvailablePort(DefaultApiValidationPort, false, cancellationToken).ConfigureAwait(false) ?? dmPort; + + return new Models.Instance + { + ConfigurationType = initialSettings.ConfigurationType ?? ConfigurationType.Disallowed, + DreamDaemonSettings = new DreamDaemonSettings + { + AllowWebClient = false, + AutoStart = false, + Port = ddPort, + SecurityLevel = DreamDaemonSecurity.Safe, + StartupTimeout = 60, + HeartbeatSeconds = 60, + TopicRequestTimeout = generalConfiguration.ByondTopicTimeout, + AdditionalParameters = String.Empty, + }, + DreamMakerSettings = new DreamMakerSettings + { + ApiValidationPort = dmPort, + ApiValidationSecurityLevel = DreamDaemonSecurity.Safe, + RequireDMApiValidation = true, + }, + Name = initialSettings.Name, + Online = false, + Path = initialSettings.Path, + AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0, + ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit, + RepositorySettings = new RepositorySettings + { + CommitterEmail = Components.Repository.Repository.DefaultCommitterEmail, + CommitterName = Components.Repository.Repository.DefaultCommitterName, + PushTestMergeCommits = false, + ShowTestMergeCommitters = false, + AutoUpdatesKeepTestMerges = false, + AutoUpdatesSynchronize = false, + PostTestMergeComment = false, + CreateGitHubDeployments = false, + }, + InstancePermissionSets = new List // give this user full privileges on the instance + { + InstanceAdminPermissionSet(null), + }, + SwarmIdentifer = swarmConfiguration.Identifier, + }; + } + + /// + /// Generate an with full rights. + /// + /// An optional existing to update. + /// or a new with full rights. + InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet permissionSetToModify) + { + if (permissionSetToModify == null) + permissionSetToModify = new InstancePermissionSet() + { + PermissionSetId = AuthenticationContext.PermissionSet.Id.Value, + }; + permissionSetToModify.ByondRights = RightsHelper.AllRights(); + permissionSetToModify.ChatBotRights = RightsHelper.AllRights(); + permissionSetToModify.ConfigurationRights = RightsHelper.AllRights(); + permissionSetToModify.DreamDaemonRights = RightsHelper.AllRights(); + permissionSetToModify.DreamMakerRights = RightsHelper.AllRights(); + permissionSetToModify.RepositoryRights = RightsHelper.AllRights(); + permissionSetToModify.InstancePermissionSetRights = RightsHelper.AllRights(); + return permissionSetToModify; + } + + /// + /// Normalize a given for an instance. + /// + /// The path to normalize. + /// The normalized . + string NormalizePath(string path) + { + if (path == null) + return null; + + path = ioManager.ResolvePath(path); + if (platformIdentifier.IsWindows) + path = path.ToUpperInvariant().Replace('\\', '/'); + + return path; + } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs index 2414b8fa50..c279536d37 100644 --- a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -14,6 +16,7 @@ using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; + using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Controllers @@ -25,12 +28,12 @@ namespace Tgstation.Server.Host.Controllers public sealed class InstancePermissionSetController : InstanceRequiredController { /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The for the - /// The for the - /// The for the + /// The for the . + /// The for the . + /// The for the . public InstancePermissionSetController( IInstanceManager instanceManager, IDatabaseContext databaseContext, @@ -41,7 +44,8 @@ namespace Tgstation.Server.Host.Controllers databaseContext, authenticationContextFactory, logger) - { } + { + } /// /// Create an . @@ -67,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers .Where(x => x.Id == model.PermissionSetId) .Select(x => new Models.PermissionSet { - UserId = x.UserId + UserId = x.UserId, }) .FirstOrDefaultAsync(cancellationToken) .ConfigureAwait(false); @@ -107,7 +111,7 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return Created(dbUser.ToApi()); } - #pragma warning restore CA1506 +#pragma warning restore CA1506 /// /// Update the permissions for an . @@ -121,7 +125,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstancePermissionSetRights.Write)] [ProducesResponseType(typeof(InstancePermissionSetResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public async Task Update([FromBody] InstancePermissionSetRequest model, CancellationToken cancellationToken) { if (model == null) @@ -154,7 +158,7 @@ namespace Tgstation.Server.Host.Controllers ? originalPermissionSet.ToApi() : new InstancePermissionSetResponse { - PermissionSetId = originalPermissionSet.PermissionSetId + PermissionSetId = originalPermissionSet.PermissionSetId, }); } #pragma warning restore CA1506 diff --git a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs index 7ed3b9ab75..20f0387255 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs @@ -1,9 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Serilog.Context; -using System; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components; @@ -23,12 +25,46 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Initializes a new instance of the . + /// Corrects discrepencies between the status of s in the database vs the service. + /// + /// The to use. + /// The to use. + /// The to check. + /// if an unsaved DB update was made, otherwise. + public static bool ValidateInstanceOnlineStatus(IInstanceManager instanceManager, ILogger logger, Api.Models.Instance metadata) + { + if (instanceManager == null) + throw new ArgumentNullException(nameof(instanceManager)); + if (metadata == null) + throw new ArgumentNullException(nameof(metadata)); + + bool online; + using (var instanceReferenceCheck = instanceManager.GetInstanceReference(metadata)) + online = instanceReferenceCheck != null; + + if (metadata.Online.Value == online) + return false; + + const string OfflineWord = "offline"; + const string OnlineWord = "online"; + + logger.LogWarning( + "Instance {0} is says it's {1} in the database, but it is actually {2} in the service. Updating the database to reflect this...", + metadata.Id, + online ? OfflineWord : OnlineWord, + online ? OnlineWord : OfflineWord); + + metadata.Online = online; + return true; + } + + /// + /// Initializes a new instance of the class. /// /// The value of . /// The for the . /// The for the . - /// The for the + /// The for the . protected InstanceRequiredController( IInstanceManager instanceManager, IDatabaseContext databaseContext, @@ -79,39 +115,5 @@ namespace Tgstation.Server.Host.Controllers return await action(instanceReference).ConfigureAwait(false); } } - - /// - /// Corrects discrepencies between the status of s in the database vs the service. - /// - /// The to use. - /// The to use. - /// The to check. - /// if an unsaved DB update was made, otherwise. - public static bool ValidateInstanceOnlineStatus(IInstanceManager instanceManager, ILogger logger, Api.Models.Instance metadata) - { - if (instanceManager == null) - throw new ArgumentNullException(nameof(instanceManager)); - if (metadata == null) - throw new ArgumentNullException(nameof(metadata)); - - bool online; - using (var instanceReferenceCheck = instanceManager.GetInstanceReference(metadata)) - online = instanceReferenceCheck != null; - - if (metadata.Online.Value == online) - return false; - - const string OfflineWord = "offline"; - const string OnlineWord = "online"; - - logger.LogWarning( - "Instance {0} is says it's {1} in the database, but it is actually {2} in the service. Updating the database to reflect this...", - metadata.Id, - online ? OfflineWord : OnlineWord, - online ? OnlineWord : OfflineWord); - - metadata.Online = online; - return true; - } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 49613a329f..1314d498e4 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -17,24 +19,24 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// for s + /// for s. /// [Route(Routes.Jobs)] public sealed class JobController : InstanceRequiredController { /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . - /// The for the - /// The for the - /// The value of - /// The for the + /// The for the . + /// The for the . + /// The value of . + /// The for the . public JobController( IInstanceManager instanceManager, IDatabaseContext databaseContext, @@ -112,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers /// A resulting in the of the request. /// cancellation requested successfully. /// does not exist in this instance. - /// could not be found in the job manager. Has it already completed? + /// could not be found in the job manager. Has it already completed?. [HttpDelete("{id}")] [TgsAuthorize] [ProducesResponseType(typeof(JobResponse), 202)] diff --git a/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResult.cs b/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResult.cs index eefafb2aa6..71ebc2eded 100644 --- a/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResult.cs +++ b/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResult.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.IO; using System.Net.Mime; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + namespace Tgstation.Server.Host.Controllers { /// @@ -19,7 +20,7 @@ namespace Tgstation.Server.Host.Controllers public FileStream FileStream { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public LimitedFileStreamResult(FileStream stream) diff --git a/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResultExecutor.cs b/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResultExecutor.cs index 19df00ae1e..bce07a46b5 100644 --- a/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResultExecutor.cs +++ b/src/Tgstation.Server.Host/Controllers/LimitedFileStreamResultExecutor.cs @@ -1,10 +1,11 @@ +using System; +using System.IO; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Threading.Tasks; namespace Tgstation.Server.Host.Controllers { @@ -14,7 +15,7 @@ namespace Tgstation.Server.Host.Controllers public class LimitedFileStreamResultExecutor : FileResultExecutorBase, IActionResultExecutor { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . public LimitedFileStreamResultExecutor(ILogger logger) diff --git a/src/Tgstation.Server.Host/Controllers/PaginatableResult.cs b/src/Tgstation.Server.Host/Controllers/PaginatableResult.cs index 7b235ee01b..ae19ba913e 100644 --- a/src/Tgstation.Server.Host/Controllers/PaginatableResult.cs +++ b/src/Tgstation.Server.Host/Controllers/PaginatableResult.cs @@ -1,7 +1,8 @@ -using Microsoft.AspNetCore.Mvc; -using System; +using System; using System.Linq; +using Microsoft.AspNetCore.Mvc; + namespace Tgstation.Server.Host.Controllers { /// @@ -21,7 +22,7 @@ namespace Tgstation.Server.Host.Controllers public IActionResult EarlyOut { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public PaginatableResult(IOrderedQueryable results) @@ -30,7 +31,7 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public PaginatableResult(IActionResult earlyOut) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index ec9b99d5e1..2652df6067 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -9,6 +6,11 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -27,28 +29,28 @@ namespace Tgstation.Server.Host.Controllers /// for managing the git repository. /// [Route(Routes.Repository)] - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public sealed class RepositoryController : InstanceRequiredController { /// - /// The for the + /// The for the . /// readonly IGitHubClientFactory gitHubClientFactory; /// - /// The for the + /// The for the . /// readonly IJobManager jobManager; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The for the . - /// The value of - /// The value of - /// The for the + /// The value of . + /// The value of . + /// The for the . public RepositoryController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -66,69 +68,6 @@ namespace Tgstation.Server.Host.Controllers this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } - async Task LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) - { - var repoSha = repository.Head; - - IQueryable ApplyQuery(IQueryable query) => query - .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id) - .Include(x => x.CompileJobs) - .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy); - - var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - - // If the DB doesn't have it, check the local set - if (revisionInfo == default) - revisionInfo = databaseContext - .RevisionInformations - .Local - .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id) - .FirstOrDefault(); - - var needsDbUpdate = revisionInfo == default; - if (needsDbUpdate) - { - // needs insertion - revisionInfo = new Models.RevisionInformation - { - Instance = instance, - CommitSha = repoSha, - Timestamp = await repository.TimestampCommit(repoSha, cancellationToken).ConfigureAwait(false), - CompileJobs = new List(), - ActiveTestMerges = new List() // non null vals for api returns - }; - - lock (databaseContext) // cleaner this way - databaseContext.RevisionInformations.Add(revisionInfo); - } - - revisionInfo.OriginCommitSha ??= lastOriginCommitSha; - if (revisionInfo.OriginCommitSha == null) - { - revisionInfo.OriginCommitSha = repoSha; - Logger.LogInformation(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha); - } - - revInfoSink?.Invoke(revisionInfo); - return needsDbUpdate; - } - - async Task PopulateApi(RepositoryResponse model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken) - { - model.RemoteGitProvider = repository.RemoteGitProvider; - model.RemoteRepositoryOwner = repository.RemoteRepositoryOwner; - model.RemoteRepositoryName = repository.RemoteRepositoryName; - - model.Origin = repository.Origin; - model.Reference = repository.Reference; - - // rev info stuff - Models.RevisionInformation revisionInfo = null; - var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); - model.RevisionInformation = revisionInfo.ToApi(); - return needsDbUpdate; - } - /// /// Begin cloning the repository if it doesn't exist. /// @@ -196,36 +135,40 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Repository, CancelRight = (ulong)RepositoryRights.CancelClone, - Instance = Instance + Instance = Instance, }; var api = currentModel.ToApi(); - await jobManager.RegisterOperation(job, async (core, databaseContextFactory, paramJob, progressReporter, ct) => - { - var repoManager = core.RepositoryManager; - using var repos = await repoManager.CloneRepository( - origin, - cloneBranch, - currentModel.AccessUser, - currentModel.AccessToken, - progressReporter, - model.RecurseSubmodules ?? true, - ct) - .ConfigureAwait(false); - if (repos == null) - throw new JobException(ErrorCode.RepoExists); - var instance = new Models.Instance + await jobManager.RegisterOperation( + job, + async (core, databaseContextFactory, paramJob, progressReporter, ct) => { - Id = Instance.Id - }; - await databaseContextFactory.UseContext( - async databaseContext => + var repoManager = core.RepositoryManager; + using var repos = await repoManager.CloneRepository( + origin, + cloneBranch, + currentModel.AccessUser, + currentModel.AccessToken, + progressReporter, + model.RecurseSubmodules ?? true, + ct) + .ConfigureAwait(false); + if (repos == null) + throw new JobException(ErrorCode.RepoExists); + var instance = new Models.Instance { - databaseContext.Instances.Attach(instance); - if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false)) - await databaseContext.Save(ct).ConfigureAwait(false); - }) - .ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + Id = Instance.Id, + }; + await databaseContextFactory.UseContext( + async databaseContext => + { + databaseContext.Instances.Attach(instance); + if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false)) + await databaseContext.Save(ct).ConfigureAwait(false); + }) + .ConfigureAwait(false); + }, + cancellationToken) + .ConfigureAwait(false); api.Origin = model.Origin; api.Reference = model.Reference; @@ -239,8 +182,8 @@ namespace Tgstation.Server.Host.Controllers /// /// Delete the repository. /// - /// The for the operation - /// A resulting in the of the operation + /// The for the operation. + /// A resulting in the of the operation. /// Job to delete the repository created successfully. /// The database entity for the requested instance could not be retrieved. The instance was likely detached. [HttpDelete] @@ -270,7 +213,7 @@ namespace Tgstation.Server.Host.Controllers { Description = "Delete repository", StartedBy = AuthenticationContext.User, - Instance = Instance + Instance = Instance, }; var api = currentModel.ToApi(); await jobManager.RegisterOperation( @@ -465,12 +408,28 @@ namespace Tgstation.Server.Host.Controllers description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha); if (newTestMerges) - description = String.Format(CultureInfo.InvariantCulture, "{0}est merge(s) {1}{2}", - description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T", - String.Join(", ", model.NewTestMerges.Select(x => - String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, - x.TargetCommitSha != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.TargetCommitSha.Substring(0, 7)) : String.Empty))), - description != null ? String.Empty : " in repository"); + description = String.Format( + CultureInfo.InvariantCulture, + "{0}est merge(s) {1}{2}", + description != null + ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) + : "T", + String.Join( + ", ", + model.NewTestMerges.Select( + x => String.Format( + CultureInfo.InvariantCulture, + "#{0}{1}", + x.Number, + x.TargetCommitSha != null + ? String.Format( + CultureInfo.InvariantCulture, + " at {0}", + x.TargetCommitSha.Substring(0, 7)) + : String.Empty))), + description != null + ? String.Empty + : " in repository"); if (description == null) return Json(api); // no git changes @@ -518,7 +477,7 @@ namespace Tgstation.Server.Host.Controllers var attachedInstance = new Models.Instance { - Id = Instance.Id + Id = Instance.Id, }; Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory @@ -544,7 +503,7 @@ namespace Tgstation.Server.Host.Controllers { mergedBy = new User { - Id = AuthenticationContext.User.Id + Id = AuthenticationContext.User.Id, }; databaseContext.Users.Attach(mergedBy); @@ -557,7 +516,7 @@ namespace Tgstation.Server.Host.Controllers lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge { - TestMerge = testMergeToAdd + TestMerge = testMergeToAdd, }); lastRevisionInfo.PrimaryTestMerge = testMergeToAdd; @@ -659,8 +618,8 @@ namespace Tgstation.Server.Host.Controllers throw new JobException(ErrorCode.RepoTestMergeInvalidRemote); // bit of sanitization - foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha))) - I.TargetCommitSha = null; + foreach (var newTestMergeWithoutTargetCommitSha in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha))) + newTestMergeWithoutTargetCommitSha.TargetCommitSha = null; var gitHubClient = currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) @@ -675,20 +634,20 @@ namespace Tgstation.Server.Host.Controllers if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha) { bool cantSearch = false; - foreach (var I in model.NewTestMerges) + foreach (var newTestMerge in model.NewTestMerges) { - if (I.TargetCommitSha != null) + if (newTestMerge.TargetCommitSha != null) #pragma warning disable CA1308 // Normalize strings to uppercase - I.TargetCommitSha = I.TargetCommitSha?.ToLowerInvariant(); // ala libgit2 + newTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha?.ToLowerInvariant(); // ala libgit2 #pragma warning restore CA1308 // Normalize strings to uppercase else try { // retrieve the latest sha - var pr = await repo.GetTestMerge(I, currentModel, ct).ConfigureAwait(false); + var pr = await repo.GetTestMerge(newTestMerge, currentModel, ct).ConfigureAwait(false); // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it - I.TargetCommitSha = pr.TargetCommitSha; + newTestMerge.TargetCommitSha = pr.TargetCommitSha; } catch { @@ -728,14 +687,14 @@ namespace Tgstation.Server.Host.Controllers if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1) { // okay try to add at least SOME prs we've seen before - var search = model.NewTestMerges.ToList(); + var listedNewTestMerges = model.NewTestMerges.ToList(); var appliedTestMergeIds = new List(); Models.RevisionInformation lastGoodRevInfo = null; do { - foreach (var I in search) + foreach (var newTestMergeParameters in listedNewTestMerges) { revInfoWereLookingFor = dbPull .Where(testRevInfo => @@ -775,17 +734,17 @@ namespace Tgstation.Server.Host.Controllers { lastGoodRevInfo = revInfoWereLookingFor; appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id); - search.Remove(I); + listedNewTestMerges.Remove(newTestMergeParameters); break; } } } - while (revInfoWereLookingFor != null && search.Count > 0); + while (revInfoWereLookingFor != null && listedNewTestMerges.Count > 0); revInfoWereLookingFor = lastGoodRevInfo; - needToApplyRemainingPrs = search.Count != 0; + needToApplyRemainingPrs = listedNewTestMerges.Count != 0; if (needToApplyRemainingPrs) - model.NewTestMerges = search; + model.NewTestMerges = listedNewTestMerges; } else if (revInfoWereLookingFor != null) needToApplyRemainingPrs = false; @@ -802,15 +761,15 @@ namespace Tgstation.Server.Host.Controllers if (needToApplyRemainingPrs) { - foreach (var I in model.NewTestMerges) + foreach (var newTestMerge in model.NewTestMerges) { - if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number)) + if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == newTestMerge.Number)) throw new JobException(ErrorCode.RepoDuplicateTestMerge); - var fullTestMergeTask = repo.GetTestMerge(I, currentModel, ct); + var fullTestMergeTask = repo.GetTestMerge(newTestMerge, currentModel, ct); var mergeResult = await repo.AddTestMerge( - I, + newTestMerge, committerName, currentModel.CommitterEmail, currentModel.AccessUser, @@ -822,7 +781,7 @@ namespace Tgstation.Server.Host.Controllers throw new JobException( ErrorCode.RepoTestMergeConflict, new JobException( - $"Test Merge #{I.Number} at {I.TargetCommitSha.Substring(0, 7)} conflicted!")); + $"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha.Substring(0, 7)} conflicted!")); Models.TestMerge fullTestMerge; try @@ -831,7 +790,7 @@ namespace Tgstation.Server.Host.Controllers } catch (Exception ex) { - Logger.LogWarning("Error retrieving metadata for test merge #{0}!", I.Number); + Logger.LogWarning("Error retrieving metadata for test merge #{0}!", newTestMerge.Number); fullTestMerge = new Models.TestMerge { @@ -839,14 +798,14 @@ namespace Tgstation.Server.Host.Controllers BodyAtMerge = ex.Message, MergedAt = DateTimeOffset.UtcNow, TitleAtMerge = ex.Message, - Comment = I.Comment, - Number = I.Number, - Url = ex.Message + Comment = newTestMerge.Comment, + Number = newTestMerge.Number, + Url = ex.Message, }; } // Ensure we're getting the full sha from git itself - fullTestMerge.TargetCommitSha = I.TargetCommitSha; + fullTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha; // MergedBy will be set later ++doneSteps; @@ -913,6 +872,99 @@ namespace Tgstation.Server.Host.Controllers api.ActiveJob = job.ToApi(); return Accepted(api); } - #pragma warning restore CA1502, CA1505 +#pragma warning restore CA1502, CA1505 + + /// + /// Load the for the current state into a given . + /// + /// The . + /// The active . + /// The active . + /// The last known origin commit SHA of the if any. + /// An optional to receive the loaded . + /// The for the operation. + /// A resulting in if the was modified in a way that requires saving, otherwise. + async Task LoadRevisionInformation( + Components.Repository.IRepository repository, + IDatabaseContext databaseContext, + Models.Instance instance, + string lastOriginCommitSha, + Action revInfoSink, + CancellationToken cancellationToken) + { + var repoSha = repository.Head; + + IQueryable ApplyQuery(IQueryable query) => query + .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id) + .Include(x => x.CompileJobs) + .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy); + + var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + // If the DB doesn't have it, check the local set + if (revisionInfo == default) + revisionInfo = databaseContext + .RevisionInformations + .Local + .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id) + .FirstOrDefault(); + + var needsDbUpdate = revisionInfo == default; + if (needsDbUpdate) + { + // needs insertion + revisionInfo = new Models.RevisionInformation + { + Instance = instance, + CommitSha = repoSha, + Timestamp = await repository.TimestampCommit(repoSha, cancellationToken).ConfigureAwait(false), + CompileJobs = new List(), + ActiveTestMerges = new List(), // non null vals for api returns + }; + + lock (databaseContext) // cleaner this way + databaseContext.RevisionInformations.Add(revisionInfo); + } + + revisionInfo.OriginCommitSha ??= lastOriginCommitSha; + if (revisionInfo.OriginCommitSha == null) + { + revisionInfo.OriginCommitSha = repoSha; + Logger.LogInformation(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha); + } + + revInfoSink?.Invoke(revisionInfo); + return needsDbUpdate; + } + + /// + /// Populate a given with the current state of a given . + /// + /// The to populate. + /// The . + /// The active . + /// The active . + /// The for the operation. + /// A resulting in if the was modified in a way that requires saving, otherwise. + async Task PopulateApi( + RepositoryResponse apiResponse, + Components.Repository.IRepository repository, + IDatabaseContext databaseContext, + Models.Instance instance, + CancellationToken cancellationToken) + { + apiResponse.RemoteGitProvider = repository.RemoteGitProvider; + apiResponse.RemoteRepositoryOwner = repository.RemoteRepositoryOwner; + apiResponse.RemoteRepositoryName = repository.RemoteRepositoryName; + + apiResponse.Origin = repository.Origin; + apiResponse.Reference = repository.Reference; + + // rev info stuff + Models.RevisionInformation revisionInfo = null; + var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); + apiResponse.RevisionInformation = revisionInfo.ToApi(); + return needsDbUpdate; + } } } diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs index 18659b6972..1b5c494704 100644 --- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs +++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs @@ -1,14 +1,16 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Serilog.Context; -using System; +using System; using System.Linq; using System.Net; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Serilog.Context; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Swarm; using Tgstation.Server.Host.System; @@ -50,7 +52,7 @@ namespace Tgstation.Server.Host.Controllers readonly SwarmConfiguration swarmConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -68,19 +70,13 @@ namespace Tgstation.Server.Host.Controllers this.logger = logger; } - /// - /// Check that the is valid. - /// - /// if the is valid, otherwise. - bool ValidateRegistration() => swarmOperations.ValidateRegistration(RequestRegistrationId); - /// /// Registration endpoint. /// /// The . /// The of the operation. [HttpPost(SwarmConstants.RegisterRoute)] - public IActionResult Register([FromBody]SwarmRegistrationRequest registrationRequest) + public IActionResult Register([FromBody] SwarmRegistrationRequest registrationRequest) { if (registrationRequest == null) throw new ArgumentNullException(nameof(registrationRequest)); @@ -128,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers /// The . /// The of the operation. [HttpPost] - public IActionResult UpdateNodeList([FromBody]SwarmServersUpdateRequest serversUpdateRequest) + public IActionResult UpdateNodeList([FromBody] SwarmServersUpdateRequest serversUpdateRequest) { if (serversUpdateRequest == null) throw new ArgumentNullException(nameof(serversUpdateRequest)); @@ -147,7 +143,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation. /// A resulting in the of the operation. [HttpPut(SwarmConstants.UpdateRoute)] - public async Task PrepareUpdate([FromBody]SwarmUpdateRequest updateRequest, CancellationToken cancellationToken) + public async Task PrepareUpdate([FromBody] SwarmUpdateRequest updateRequest, CancellationToken cancellationToken) { if (updateRequest == null) throw new ArgumentNullException(nameof(updateRequest)); @@ -197,47 +193,55 @@ namespace Tgstation.Server.Host.Controllers /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - using var _ = LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}"); - logger.LogTrace("Swarm request from {0}...", Request.HttpContext.Connection.RemoteIpAddress); - if (swarmConfiguration.PrivateKey == null) + using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) { - logger.LogDebug("Attempted swarm request without private key configured!"); - await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); - return; + logger.LogTrace("Swarm request from {0}...", Request.HttpContext.Connection.RemoteIpAddress); + if (swarmConfiguration.PrivateKey == null) + { + logger.LogDebug("Attempted swarm request without private key configured!"); + await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + if (!(Request.Headers.TryGetValue(SwarmConstants.ApiKeyHeader, out var apiKeyHeaderValues) + && apiKeyHeaderValues.Count == 1 + && apiKeyHeaderValues.First() == swarmConfiguration.PrivateKey)) + { + logger.LogDebug("Unauthorized swarm request!"); + await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + if (!(Request.Headers.TryGetValue(SwarmConstants.RegistrationIdHeader, out var registrationHeaderValues) + && registrationHeaderValues.Count == 1 + && Guid.TryParse(registrationHeaderValues.First(), out var registrationId))) + { + logger.LogDebug("Swarm request without registration ID!"); + await BadRequest().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + // we validate the registration itself on a case-by-case basis + if (ModelState?.IsValid == false) + { + var errors = ModelState + .SelectMany(x => x.Value.Errors) + .Select(x => x.Exception); + + logger.LogDebug(new AggregateException(errors), "Swarm request model validation failed!"); + await BadRequest().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + + logger.LogTrace("Starting swarm request processing..."); + await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } - - if (!(Request.Headers.TryGetValue(SwarmConstants.ApiKeyHeader, out var apiKeyHeaderValues) - && apiKeyHeaderValues.Count == 1 - && apiKeyHeaderValues.First() == swarmConfiguration.PrivateKey)) - { - logger.LogDebug("Unauthorized swarm request!"); - await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - - if (!(Request.Headers.TryGetValue(SwarmConstants.RegistrationIdHeader, out var registrationHeaderValues) - && registrationHeaderValues.Count == 1 - && Guid.TryParse(registrationHeaderValues.First(), out var registrationId))) - { - logger.LogDebug("Swarm request without registration ID!"); - await BadRequest().ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - - // we validate the registration itself on a case-by-case basis - if (ModelState?.IsValid == false) - { - var errors = ModelState - .SelectMany(x => x.Value.Errors) - .Select(x => x.Exception); - - logger.LogDebug(new AggregateException(errors), "Swarm request model validation failed!"); - await BadRequest().ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - - logger.LogTrace("Starting swarm request processing..."); - await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } + + /// + /// Check that the is valid. + /// + /// if the is valid, otherwise. + bool ValidateRegistration() => swarmOperations.ValidateRegistration(RequestRegistrationId); } } diff --git a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs index b7e5f74ec1..a16cf7646d 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs @@ -1,13 +1,15 @@ using System; + using Microsoft.AspNetCore.Authorization; + using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Host.Controllers { /// - /// Helper for using the with the system + /// Helper for using the with the system. /// - #pragma warning disable CA1019 +#pragma warning disable CA1019 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] sealed class TgsAuthorizeAttribute : AuthorizeAttribute { @@ -17,14 +19,16 @@ namespace Tgstation.Server.Host.Controllers public RightsType? RightsType { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// - public TgsAuthorizeAttribute() { } + public TgsAuthorizeAttribute() + { + } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(AdministrationRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -32,9 +36,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -42,9 +46,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(RepositoryRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -52,9 +56,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(ByondRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -62,9 +66,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(DreamMakerRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -72,9 +76,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -82,9 +86,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(ChatBotRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -92,9 +96,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(ConfigurationRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); @@ -102,9 +106,9 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Construct a for + /// Initializes a new instance of the class. /// - /// The rights required + /// The required. public TgsAuthorizeAttribute(InstancePermissionSetRights requiredRights) { Roles = RightsHelper.RoleNames(requiredRights); diff --git a/src/Tgstation.Server.Host/Controllers/TransferController.cs b/src/Tgstation.Server.Host/Controllers/TransferController.cs index edaed2ac8a..2b12f82898 100644 --- a/src/Tgstation.Server.Host/Controllers/TransferController.cs +++ b/src/Tgstation.Server.Host/Controllers/TransferController.cs @@ -1,14 +1,16 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -31,12 +33,12 @@ namespace Tgstation.Server.Host.Controllers readonly IFileTransferStreamHandler fileTransferService; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The value of . - /// The for the + /// The for the . public TransferController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -72,12 +74,12 @@ namespace Tgstation.Server.Host.Controllers if (!Request.GetTypedHeaders().Accept.Any(x => streamAccept.IsSubsetOf(x))) return StatusCode(HttpStatusCode.NotAcceptable, new ErrorMessageResponse(ErrorCode.BadHeaders) { - AdditionalData = $"File downloads must accept both {MediaTypeNames.Application.Octet} and {MediaTypeNames.Application.Json}!" + AdditionalData = $"File downloads must accept both {MediaTypeNames.Application.Octet} and {MediaTypeNames.Application.Json}!", }); var fileTicketResult = new FileTicketResponse { - FileTicket = ticket + FileTicket = ticket, }; var tuple = await fileTransferService.RetrieveDownloadStream(fileTicketResult, cancellationToken).ConfigureAwait(false); @@ -119,7 +121,7 @@ namespace Tgstation.Server.Host.Controllers var fileTicketResult = new FileTicketResponse { - FileTicket = ticket + FileTicket = ticket, }; var result = await fileTransferService.SetUploadStream(fileTicketResult, Request.Body, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 00dcc3667b..7356e1f04f 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -1,12 +1,14 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -26,29 +28,29 @@ namespace Tgstation.Server.Host.Controllers public sealed class UserController : ApiController { /// - /// The for the + /// The for the . /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the . /// readonly ICryptographySuite cryptographySuite; /// - /// The for the + /// The for the . /// readonly GeneralConfiguration generalConfiguration; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The value of - /// The value of + /// The for the . + /// The for the . + /// The value of . + /// The value of . /// The for the . - /// The containing the value of + /// The containing the value of . public UserController( IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, @@ -67,43 +69,6 @@ namespace Tgstation.Server.Host.Controllers generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } - /// - /// Check if a given has a valid specified. - /// - /// The to check. - /// If this is a new . - /// if is valid, a otherwise. - BadRequestObjectResult CheckValidName(UserUpdateRequest model, bool newUser) - { - var userInvalidWithNullName = newUser && model.Name == null && model.SystemIdentifier == null; - if (userInvalidWithNullName || (model.Name != null && String.IsNullOrWhiteSpace(model.Name))) - return BadRequest(new ErrorMessageResponse(ErrorCode.UserMissingName)); - - model.Name = model.Name?.Trim(); - if (model.Name != null && model.Name.Contains(':', StringComparison.InvariantCulture)) - return BadRequest(new ErrorMessageResponse(ErrorCode.UserColonInName)); - return null; - } - - /// - /// Attempt to change the password of a given . - /// - /// The user to update. - /// The new password. - /// If this is for a new . - /// on success, if is too short. - BadRequestObjectResult TrySetPassword(User dbUser, string newPassword, bool newUser) - { - newPassword ??= String.Empty; - if (newPassword.Length < generalConfiguration.MinimumPasswordLength) - return BadRequest(new ErrorMessageResponse(ErrorCode.UserPasswordLength) - { - AdditionalData = $"Required password length: {generalConfiguration.MinimumPasswordLength}" - }); - cryptographySuite.SetUserPassword(dbUser, newPassword, newUser); - return null; - } - /// /// Create a new . /// @@ -287,7 +252,7 @@ namespace Tgstation.Server.Host.Controllers originalUser.OAuthConnections.Add(new Models.OAuthConnection { Provider = updatedConnection.Provider, - ExternalUserId = updatedConnection.ExternalUserId + ExternalUserId = updatedConnection.ExternalUserId, }); } @@ -464,11 +429,48 @@ namespace Tgstation.Server.Host.Controllers ?.Select(x => new Models.OAuthConnection { Provider = x.Provider, - ExternalUserId = x.ExternalUserId + ExternalUserId = x.ExternalUserId, }) .ToList() ?? new List(), }; } + + /// + /// Check if a given has a valid specified. + /// + /// The to check. + /// If this is a new . + /// if is valid, a otherwise. + BadRequestObjectResult CheckValidName(UserUpdateRequest model, bool newUser) + { + var userInvalidWithNullName = newUser && model.Name == null && model.SystemIdentifier == null; + if (userInvalidWithNullName || (model.Name != null && String.IsNullOrWhiteSpace(model.Name))) + return BadRequest(new ErrorMessageResponse(ErrorCode.UserMissingName)); + + model.Name = model.Name?.Trim(); + if (model.Name != null && model.Name.Contains(':', StringComparison.InvariantCulture)) + return BadRequest(new ErrorMessageResponse(ErrorCode.UserColonInName)); + return null; + } + + /// + /// Attempt to change the password of a given . + /// + /// The user to update. + /// The new password. + /// If this is for a new . + /// on success, if is too short. + BadRequestObjectResult TrySetPassword(User dbUser, string newPassword, bool newUser) + { + newPassword ??= String.Empty; + if (newPassword.Length < generalConfiguration.MinimumPasswordLength) + return BadRequest(new ErrorMessageResponse(ErrorCode.UserPasswordLength) + { + AdditionalData = $"Required password length: {generalConfiguration.MinimumPasswordLength}", + }); + cryptographySuite.SetUserPassword(dbUser, newPassword, newUser); + return null; + } } } diff --git a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs index d2e3cf8c7e..30a3461812 100644 --- a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs @@ -1,11 +1,13 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -15,6 +17,7 @@ using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; + using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Controllers @@ -31,10 +34,10 @@ namespace Tgstation.Server.Host.Controllers readonly GeneralConfiguration generalConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The for the - /// The for the + /// The for the . + /// The for the . /// The containing the value of . /// The for the . public UserGroupController( @@ -80,7 +83,7 @@ namespace Tgstation.Server.Host.Controllers var permissionSet = new Models.PermissionSet { AdministrationRights = model.PermissionSet?.AdministrationRights ?? AdministrationRights.None, - InstanceManagerRights = model.PermissionSet?.InstanceManagerRights ?? InstanceManagerRights.None + InstanceManagerRights = model.PermissionSet?.InstanceManagerRights ?? InstanceManagerRights.None, }; var dbGroup = new UserGroup @@ -137,7 +140,7 @@ namespace Tgstation.Server.Host.Controllers if (!AuthenticationContext.PermissionSet.AdministrationRights.Value.HasFlag(AdministrationRights.ReadUsers)) return Json(new UserGroupResponse { - Id = currentGroup.Id + Id = currentGroup.Id, }); return Json(currentGroup.ToApi(true)); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 1737b56d04..d9b4928689 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,3 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Threading.Tasks; + using Cyberboss.AspNetCore.AsyncInitializer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; @@ -15,12 +22,7 @@ using Newtonsoft.Json; using Serilog; using Serilog.Events; using Serilog.Formatting.Display; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components; @@ -61,12 +63,36 @@ namespace Tgstation.Server.Host.Core readonly IWebHostEnvironment hostingEnvironment; /// - /// The for the + /// The for the . /// ITokenFactory tokenFactory; /// - /// Construct an + /// Create the default . + /// + /// A new with the default settings. + public static IServerFactory CreateDefaultServerFactory() + => new ServerFactory( + AssemblyInformationProvider, + IOManager); + + /// + /// Adds the implementation. + /// + /// The child for the current system. + /// The to configure. + /// The to use. + static void AddWatchdog(IServiceCollection services, IPostSetupServices postSetupServices) + where TSystemWatchdogFactory : class, IWatchdogFactory + { + if (postSetupServices.GeneralConfiguration.UseBasicWatchdog) + services.AddSingleton(); + else + services.AddSingleton(); + } + + /// + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -78,15 +104,6 @@ namespace Tgstation.Server.Host.Core this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); } - /// - /// Create the default . - /// - /// A new with the default settings. - public static IServerFactory CreateDefaultServerFactory() - => new ServerFactory( - AssemblyInformationProvider, - IOManager); - /// /// Configure the 's services. /// @@ -181,7 +198,7 @@ namespace Tgstation.Server.Host.Core .GetRequiredService() .InjectClaimsIntoContext( ctx, - ctx.HttpContext.RequestAborted) + ctx.HttpContext.RequestAborted), }; }); @@ -207,7 +224,7 @@ namespace Tgstation.Server.Host.Core options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; options.SerializerSettings.Converters = new List { - new VersionConverter() + new VersionConverter(), }; }); @@ -338,35 +355,16 @@ namespace Tgstation.Server.Host.Core } /// - /// Adds the implementation. + /// Configure the . /// - /// The child for the current system. - /// The to configure. - /// The to use. - static void AddWatchdog(IServiceCollection services, IPostSetupServices postSetupServices) - where TSystemWatchdogFactory : class, IWatchdogFactory - { - if (postSetupServices.GeneralConfiguration.UseBasicWatchdog) - services.AddSingleton(); - else - services.AddSingleton(); - } - - /// - protected override void ConfigureHostedService(IServiceCollection services) - => services.AddSingleton(x => x.GetRequiredService()); - - /// - /// Configure the - /// - /// The to configure - /// The for the - /// The value of + /// The to configure. + /// The for the . + /// The value of . /// The . /// The . - /// The containing the to use - /// The containing the to use - /// The for the + /// The containing the to use. + /// The containing the to use. + /// The for the . public void Configure( IApplicationBuilder applicationBuilder, IServerControl serverControl, @@ -465,7 +463,7 @@ namespace Tgstation.Server.Host.Core { RequestPath = ControlPanelController.ControlPanelRoute, EnableDefaultFiles = true, - EnableDirectoryBrowsing = false + EnableDirectoryBrowsing = false, }); } else @@ -489,5 +487,9 @@ namespace Tgstation.Server.Host.Core logger.LogDebug("Starting hosting on port {0}...", serverPortProvider.HttpApiPort); } + + /// + protected override void ConfigureHostedService(IServiceCollection services) + => services.AddSingleton(x => x.GetRequiredService()); } } diff --git a/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs b/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs index 9938bff220..7d76cb6bff 100644 --- a/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs +++ b/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs @@ -1,6 +1,8 @@ -using System; +using System; + using Microsoft.Extensions.Options; using Octokit; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -10,7 +12,7 @@ namespace Tgstation.Server.Host.Core sealed class GitHubClientFactory : IGitHubClientFactory { /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; @@ -20,7 +22,7 @@ namespace Tgstation.Server.Host.Core readonly GeneralConfiguration generalConfiguration; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . /// The containing the value of . @@ -30,11 +32,17 @@ namespace Tgstation.Server.Host.Core generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } + /// + public IGitHubClient CreateClient() => CreateClientImpl(generalConfiguration.GitHubAccessToken); + + /// + public IGitHubClient CreateClient(string accessToken) => CreateClientImpl(accessToken ?? throw new ArgumentNullException(nameof(accessToken))); + /// /// Create a . /// /// Optional access token to use as credentials. - /// A new + /// A new . GitHubClient CreateClientImpl(string accessToken) { var client = new GitHubClient( @@ -46,11 +54,5 @@ namespace Tgstation.Server.Host.Core return client; } - - /// - public IGitHubClient CreateClient() => CreateClientImpl(generalConfiguration.GitHubAccessToken); - - /// - public IGitHubClient CreateClient(string accessToken) => CreateClientImpl(accessToken ?? throw new ArgumentNullException(nameof(accessToken))); } } diff --git a/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs b/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs index ae0da1284d..377af13a68 100644 --- a/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs +++ b/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs @@ -5,16 +5,16 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Core { /// - /// For waiting asynchronously + /// For waiting asynchronously. /// interface IAsyncDelayer { /// - /// Create a that completes after a given + /// Create a that completes after a given . /// - /// The that must elapse - /// The for the operation - /// A representing the running operation + /// The that must elapse. + /// The for the operation. + /// A representing the running operation. Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs b/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs index d91962bf50..2f3010a413 100644 --- a/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs +++ b/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs @@ -1,9 +1,9 @@ -using Octokit; +using Octokit; namespace Tgstation.Server.Host.Core { /// - /// For creating s + /// For creating s. /// public interface IGitHubClientFactory { @@ -14,10 +14,10 @@ namespace Tgstation.Server.Host.Core IGitHubClient CreateClient(); /// - /// Create a client with authentication using a personal access token + /// Create a client with authentication using a personal access token. /// - /// The GitHub personal access token - /// A new + /// The GitHub personal access token. + /// A new . IGitHubClient CreateClient(string accessToken); } } diff --git a/src/Tgstation.Server.Host/Core/IPortAllocator.cs b/src/Tgstation.Server.Host/Core/IPortAllocator.cs index 4ab7c3b09c..b2b2697aa4 100644 --- a/src/Tgstation.Server.Host/Core/IPortAllocator.cs +++ b/src/Tgstation.Server.Host/Core/IPortAllocator.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Core diff --git a/src/Tgstation.Server.Host/Core/IRestartHandler.cs b/src/Tgstation.Server.Host/Core/IRestartHandler.cs index f3b0c3afca..c2d61d14d5 100644 --- a/src/Tgstation.Server.Host/Core/IRestartHandler.cs +++ b/src/Tgstation.Server.Host/Core/IRestartHandler.cs @@ -5,16 +5,16 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Core { /// - /// Handler for server restarts + /// Handler for server restarts. /// public interface IRestartHandler { /// - /// Handle a restart of the server + /// Handle a restart of the server. /// - /// The being updated to, if not being changed - /// The for the operation - /// A representing the running operation + /// The being updated to, if not being changed. + /// The for the operation. + /// A representing the running operation. Task HandleRestart(Version updateVersion, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/IRestartRegistration.cs b/src/Tgstation.Server.Host/Core/IRestartRegistration.cs index 78aa00ffc5..586d73f004 100644 --- a/src/Tgstation.Server.Host/Core/IRestartRegistration.cs +++ b/src/Tgstation.Server.Host/Core/IRestartRegistration.cs @@ -3,9 +3,9 @@ namespace Tgstation.Server.Host.Core { /// - /// Represents the lifetime of a registration + /// Represents the lifetime of a registration. /// public interface IRestartRegistration : IDisposable { } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Core/IServerControl.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs index a7ca77c1b5..81a72ca4c5 100644 --- a/src/Tgstation.Server.Host/Core/IServerControl.cs +++ b/src/Tgstation.Server.Host/Core/IServerControl.cs @@ -1,52 +1,53 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core { /// - /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process + /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process. /// public interface IServerControl { /// - /// if live updates are supported, . and will fail if this is + /// if live updates are supported, . and will fail if this is . /// bool WatchdogPresent { get; } /// - /// Whether or not the server is currently updating + /// Whether or not the server is currently updating. /// bool UpdateInProgress { get; } /// - /// Run a new assembly and stop the current one. This will likely trigger all active s + /// Run a new assembly and stop the current one. This will likely trigger all active s. /// - /// The the is updating to - /// The that points to the .zip file that contains the new assembly - /// The for the operation - /// if the update started successfully, if there was another update in progress + /// The the is updating to. + /// The that points to the .zip file that contains the new assembly. + /// The for the operation. + /// if the update started successfully, if there was another update in progress. bool ApplyUpdate(Version version, Uri updateZipUrl, IIOManager ioManager); /// - /// Register a given to run before stopping the server for a restart + /// Register a given to run before stopping the server for a restart. /// - /// The to register - /// A new representing the scope of the registration + /// The to register. + /// A new representing the scope of the registration. IRestartRegistration RegisterForRestart(IRestartHandler handler); /// - /// Restarts the + /// Restarts the . /// - /// A representing the running operation + /// A representing the running operation. Task Restart(); /// - /// Kill the server with a fatal exception + /// Kill the server with a fatal exception. /// - /// The to propagate to the watchdog if any - /// A representing the running operation + /// The to propagate to the watchdog if any. + /// A representing the running operation. Task Die(Exception exception); } } diff --git a/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs b/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs index 9dfc307cca..92742015f2 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs index 515f80d0c3..e5a2d9518a 100644 --- a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs +++ b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs @@ -1,8 +1,9 @@ -using Microsoft.OpenApi; +using System; + +using Microsoft.OpenApi; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; -using System; namespace Tgstation.Server.Host.Core { @@ -17,9 +18,9 @@ namespace Tgstation.Server.Host.Core readonly Type enumType; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The value of , + /// The value of ,. private OpenApiEnumVarNamesExtension(Type enumType) { this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); diff --git a/src/Tgstation.Server.Host/Core/PortAllocator.cs b/src/Tgstation.Server.Host/Core/PortAllocator.cs index 27d2bb4371..37d9049a41 100644 --- a/src/Tgstation.Server.Host/Core/PortAllocator.cs +++ b/src/Tgstation.Server.Host/Core/PortAllocator.cs @@ -1,11 +1,13 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Extensions; @@ -36,7 +38,7 @@ namespace Tgstation.Server.Host.Core readonly SwarmConfiguration swarmConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -76,22 +78,22 @@ namespace Tgstation.Server.Host.Core .ConfigureAwait(false); var exceptions = new List(); - ushort I = 0; + ushort port = 0; try { - for (I = basePort; I < UInt16.MaxValue; ++I) + for (port = basePort; port < UInt16.MaxValue; ++port) { - if (checkOne && I != basePort) + if (checkOne && port != basePort) break; - if (I == serverPortProvider.HttpApiPort - || ddPorts.Contains(I) - || dmPorts.Contains(I)) + if (port == serverPortProvider.HttpApiPort + || ddPorts.Contains(port) + || dmPorts.Contains(port)) continue; try { - SocketExtensions.BindTest(I, false); + SocketExtensions.BindTest(port, false); } catch (Exception ex) { @@ -99,8 +101,8 @@ namespace Tgstation.Server.Host.Core continue; } - logger.LogInformation("Allocated port {0}", I); - return I; + logger.LogInformation("Allocated port {0}", port); + return port; } logger.LogWarning("Unable to allocate port >= {0}!", basePort); @@ -108,7 +110,7 @@ namespace Tgstation.Server.Host.Core } finally { - logger.LogDebug(new AggregateException(exceptions), "Failed to allocate ports {0}-{1}!", basePort, I - 1); + logger.LogDebug(new AggregateException(exceptions), "Failed to allocate ports {0}-{1}!", basePort, port - 1); } } } diff --git a/src/Tgstation.Server.Host/Core/RestartRegistration.cs b/src/Tgstation.Server.Host/Core/RestartRegistration.cs index 8e6fca271e..2ebd855a95 100644 --- a/src/Tgstation.Server.Host/Core/RestartRegistration.cs +++ b/src/Tgstation.Server.Host/Core/RestartRegistration.cs @@ -6,14 +6,14 @@ namespace Tgstation.Server.Host.Core sealed class RestartRegistration : IRestartRegistration { /// - /// The + /// The . /// readonly Action onDispose; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public RestartRegistration(Action onDispose) { this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); @@ -22,4 +22,4 @@ namespace Tgstation.Server.Host.Core /// public void Dispose() => onDispose(); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs index bb517656ff..1fc7d4c400 100644 --- a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs +++ b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs @@ -5,16 +5,16 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Core { /// - /// Async lock context helper + /// Async lock context helper. /// public sealed class SemaphoreSlimContext : IDisposable { /// - /// Asyncronously locks a + /// Asyncronously locks a . /// - /// The to lock - /// The for the operation - /// A resulting in the for the lock + /// The to lock. + /// The for the operation. + /// A resulting in the for the lock. public static async Task Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken) { if (semaphore == null) @@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Core } /// - /// The locked + /// The locked . /// readonly SemaphoreSlim lockedSemaphore; @@ -35,14 +35,14 @@ namespace Tgstation.Server.Host.Core readonly object disposeLock; /// - /// If has been called + /// If has been called. /// bool disposed; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) { this.lockedSemaphore = lockedSemaphore; @@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Core #pragma warning restore CA1821 // Remove empty Finalizers /// - /// Release the lock on + /// Release the lock on . /// public void Dispose() { diff --git a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs index 04c009949d..0b5bb6cd12 100644 --- a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs +++ b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Configuration; +using System; +using System.Linq; + +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; -using System.Linq; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Core @@ -19,7 +21,7 @@ namespace Tgstation.Server.Host.Core readonly GeneralConfiguration generalConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The containing the value of . /// The to use. diff --git a/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs b/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs index eda60adc04..3b8475b1ca 100644 --- a/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs +++ b/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs @@ -1,11 +1,13 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Octokit; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Octokit; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; @@ -41,7 +43,7 @@ namespace Tgstation.Server.Host.Core readonly UpdatesConfiguration updatesConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Core/ServerUpdateResult.cs b/src/Tgstation.Server.Host/Core/ServerUpdateResult.cs index 0fe3d71303..8146c03d4a 100644 --- a/src/Tgstation.Server.Host/Core/ServerUpdateResult.cs +++ b/src/Tgstation.Server.Host/Core/ServerUpdateResult.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Core { /// /// The result of a call to start a server update. diff --git a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs index 8178a3ce24..62912708b0 100644 --- a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs +++ b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs @@ -1,15 +1,17 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Net.Http.Headers; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Mime; using System.Reflection; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -37,107 +39,6 @@ namespace Tgstation.Server.Host.Core /// const string TokenSecuritySchemeId = "Token_Authorization_Scheme"; - static void AddDefaultResponses(OpenApiDocument document) - { - var errorMessageContent = new Dictionary - { - { - MediaTypeNames.Application.Json, - new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = nameof(ErrorMessageResponse), - Type = ReferenceType.Schema - } - } - } - } - }; - - void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete) - { - string responseKey = $"{(int)code}"; - - document.Components.Responses.Add(responseKey, concrete); - - var referenceResponse = new OpenApiResponse - { - Reference = new OpenApiReference - { - Type = ReferenceType.Response, - Id = responseKey - } - }; - - foreach (var operation in document.Paths.SelectMany(path => path.Value.Operations)) - operation.Value.Responses.TryAdd(responseKey, referenceResponse); - } - - AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse - { - Description = "A badly formatted request was made. See error message for details.", - Content = errorMessageContent, - }); - - AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse - { - Description = "Invalid Authentication header." - }); - - AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse - { - Description = "User lacks sufficient permissions for the operation." - }); - - AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse - { - Description = "A data integrity check failed while performing the operation. See error message for details.", - Content = errorMessageContent - }); - - AddDefaultResponse(HttpStatusCode.NotAcceptable, new OpenApiResponse - { - Description = $"Invalid Accept header, TGS requires `{HeaderNames.Accept}: {MediaTypeNames.Application.Json}`.", - Content = errorMessageContent - }); - - AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse - { - Description = ErrorCode.InternalServerError.Describe(), - Content = errorMessageContent - }); - - AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse - { - Description = "The server may be starting up or shutting down." - }); - - AddDefaultResponse(HttpStatusCode.NotImplemented, new OpenApiResponse - { - Description = ErrorCode.RequiresPosixSystemIdentity.Describe(), - Content = errorMessageContent - }); - } - - /// - /// Generates the OpenAPI schema ID for a given . - /// - /// The to generate a schema ID for. - /// The generated schema ID for . - static string GenerateSchemaId(Type type) - { - if (type == typeof(UserName)) - return "ShallowUserResponse"; - - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PaginatedResponse<>)) - return $"Paginated{type.GenericTypeArguments.First().Name}"; - - return type.Name; - } - /// /// Configure the swagger settings. /// @@ -155,14 +56,14 @@ namespace Tgstation.Server.Host.Core License = new OpenApiLicense { Name = "AGPL-3.0", - Url = new Uri("https://github.com/tgstation/tgstation-server/blob/dev/LICENSE") + Url = new Uri("https://github.com/tgstation/tgstation-server/blob/dev/LICENSE"), }, Contact = new OpenApiContact { Name = "/tg/station 13", - Url = new Uri("https://github.com/tgstation") + Url = new Uri("https://github.com/tgstation"), }, - Description = "A production scale tool for BYOND server management" + Description = "A production scale tool for BYOND server management", }); // Important to do this before applying our own filters @@ -185,7 +86,7 @@ namespace Tgstation.Server.Host.Core In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Name = HeaderNames.Authorization, - Scheme = ApiHeaders.BasicAuthenticationScheme + Scheme = ApiHeaders.BasicAuthenticationScheme, }); swaggerGenOptions.AddSecurityDefinition(OAuthSecuritySchemeId, new OpenApiSecurityScheme @@ -193,7 +94,7 @@ namespace Tgstation.Server.Host.Core In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Name = HeaderNames.Authorization, - Scheme = ApiHeaders.OAuthAuthenticationScheme + Scheme = ApiHeaders.OAuthAuthenticationScheme, }); swaggerGenOptions.AddSecurityDefinition(TokenSecuritySchemeId, new OpenApiSecurityScheme @@ -202,219 +103,97 @@ namespace Tgstation.Server.Host.Core In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Name = HeaderNames.Authorization, - Scheme = ApiHeaders.BearerAuthenticationScheme + Scheme = ApiHeaders.BearerAuthenticationScheme, }); } - /// - public void Apply(OpenApiOperation operation, OperationFilterContext context) + /// + /// Add the default error responses to a given . + /// + /// The to augment. + static void AddDefaultResponses(OpenApiDocument document) { - if (operation == null) - throw new ArgumentNullException(nameof(operation)); - if (context == null) - throw new ArgumentNullException(nameof(context)); - - operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}"; - - var authAttributes = context - .MethodInfo - .DeclaringType - .GetCustomAttributes(true) - .Union( - context - .MethodInfo - .GetCustomAttributes(true)) - .OfType(); - - if (authAttributes.Any()) + var errorMessageContent = new Dictionary { - var tokenScheme = new OpenApiSecurityScheme { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = TokenSecuritySchemeId - } - }; - - operation.Security = new List - { - new OpenApiSecurityRequirement + MediaTypeNames.Application.Json, + new OpenApiMediaType { + Schema = new OpenApiSchema { - tokenScheme, - new List() - } - } - }; - - if (typeof(InstanceRequiredController).IsAssignableFrom(context.MethodInfo.DeclaringType)) - operation.Parameters.Insert(0, new OpenApiParameter - { - Reference = new OpenApiReference - { - Type = ReferenceType.Parameter, - Id = ApiHeaders.InstanceIdHeader - } - }); - else if (typeof(TransferController).IsAssignableFrom(context.MethodInfo.DeclaringType)) - if (context.MethodInfo.Name == nameof(TransferController.Upload)) - operation.RequestBody = new OpenApiRequestBody - { - Content = new Dictionary + Reference = new OpenApiReference { - { - MediaTypeNames.Application.Octet, - new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "string", - Format = "binary" - } - } - } - } - }; - else if (context.MethodInfo.Name == nameof(TransferController.Download)) - { - var twoHundredResponseContents = operation.Responses["200"].Content; - var fileContent = twoHundredResponseContents[MediaTypeNames.Application.Json]; - twoHundredResponseContents.Remove(MediaTypeNames.Application.Json); - twoHundredResponseContents.Add(MediaTypeNames.Application.Octet, fileContent); + Id = nameof(ErrorMessageResponse), + Type = ReferenceType.Schema, + }, + }, } - } - else if (context.MethodInfo.Name == nameof(HomeController.CreateToken)) + }, + }; + + void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete) { - var passwordScheme = new OpenApiSecurityScheme + string responseKey = $"{(int)code}"; + + document.Components.Responses.Add(responseKey, concrete); + + var referenceResponse = new OpenApiResponse { Reference = new OpenApiReference { - Type = ReferenceType.SecurityScheme, - Id = PasswordSecuritySchemeId - } + Type = ReferenceType.Response, + Id = responseKey, + }, }; - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = OAuthSecuritySchemeId - } - }; - - operation.Parameters.Add(new OpenApiParameter - { - In = ParameterLocation.Header, - Name = ApiHeaders.OAuthProviderHeader, - Description = "The external OAuth service provider.", - Style = ParameterStyle.Simple, - Example = new OpenApiString("Discord"), - Schema = new OpenApiSchema - { - Type = "string" - } - }); - - operation.Security = new List - { - new OpenApiSecurityRequirement - { - { - passwordScheme, - new List() - }, - { - oAuthScheme, - new List() - } - } - }; + foreach (var operation in document.Paths.SelectMany(path => path.Value.Operations)) + operation.Value.Responses.TryAdd(responseKey, referenceResponse); } - } - /// - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - if (swaggerDoc == null) - throw new ArgumentNullException(nameof(swaggerDoc)); - if (context == null) - throw new ArgumentNullException(nameof(context)); - - swaggerDoc.ExternalDocs = new OpenApiExternalDocs + AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse { - Description = "API Usage Documentation", - Url = new Uri("https://tgstation.github.io/tgstation-server/api.html"), - }; - - swaggerDoc.Components.Parameters.Add(ApiHeaders.InstanceIdHeader, new OpenApiParameter - { - In = ParameterLocation.Header, - Name = ApiHeaders.InstanceIdHeader, - Description = "The instance ID being accessed", - Required = true, - Style = ParameterStyle.Simple, - Schema = new OpenApiSchema - { - Type = "integer" - } + Description = "A badly formatted request was made. See error message for details.", + Content = errorMessageContent, }); - var productHeaderSchema = new OpenApiSchema + AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse { - Type = "string", - Format = "productheader" - }; - - swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter - { - In = ParameterLocation.Header, - Name = ApiHeaders.ApiVersionHeader, - Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"", - Required = true, - Style = ParameterStyle.Simple, - Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"), - Schema = productHeaderSchema + Description = "Invalid Authentication header.", }); - swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter + AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse { - In = ParameterLocation.Header, - Name = HeaderNames.UserAgent, - Description = "The user agent of the calling client.", - Required = true, - Style = ParameterStyle.Simple, - Example = new OpenApiString("Your-user-agent/1.0.0.0"), - Schema = productHeaderSchema + Description = "User lacks sufficient permissions for the operation.", }); - var allSchemas = context - .SchemaRepository - .Schemas; - foreach (var path in swaggerDoc.Paths) - foreach (var operation in path.Value.Operations.Select(x => x.Value)) - { - operation.Parameters.Insert(0, new OpenApiParameter - { - Reference = new OpenApiReference - { - Type = ReferenceType.Parameter, - Id = ApiHeaders.ApiVersionHeader - }, - }); + AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse + { + Description = "A data integrity check failed while performing the operation. See error message for details.", + Content = errorMessageContent, + }); - operation.Parameters.Insert(1, new OpenApiParameter - { - Reference = new OpenApiReference - { - Type = ReferenceType.Parameter, - Id = HeaderNames.UserAgent - } - }); - } + AddDefaultResponse(HttpStatusCode.NotAcceptable, new OpenApiResponse + { + Description = $"Invalid Accept header, TGS requires `{HeaderNames.Accept}: {MediaTypeNames.Application.Json}`.", + Content = errorMessageContent, + }); - AddDefaultResponses(swaggerDoc); + AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse + { + Description = ErrorCode.InternalServerError.Describe(), + Content = errorMessageContent, + }); + + AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse + { + Description = "The server may be starting up or shutting down.", + }); + + AddDefaultResponse(HttpStatusCode.NotImplemented, new OpenApiResponse + { + Description = ErrorCode.RequiresPosixSystemIdentity.Describe(), + Content = errorMessageContent, + }); } /// @@ -521,15 +300,243 @@ namespace Tgstation.Server.Host.Core } else if (responsePresence == FieldPresence.Required && requestOptions.All(x => x.Presence == FieldPresence.Required && !x.PutOnly)) - // special tactics - subSchema.Nullable = subSchemaId.Equals(nameof(TestMergeParameters.TargetCommitSha), StringComparison.OrdinalIgnoreCase) - && rootSchemaId == nameof(TestMergeParameters); + subSchema.Nullable = subSchemaId.Equals( + nameof(TestMergeParameters.TargetCommitSha), + StringComparison.OrdinalIgnoreCase) + && rootSchemaId == nameof(TestMergeParameters); // special tactics // otherwise, we have to assume it's a shared schema // use what Swagger thinks the nullability is by default } } + /// + /// Generates the OpenAPI schema ID for a given . + /// + /// The to generate a schema ID for. + /// The generated schema ID for . + static string GenerateSchemaId(Type type) + { + if (type == typeof(UserName)) + return "ShallowUserResponse"; + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PaginatedResponse<>)) + return $"Paginated{type.GenericTypeArguments.First().Name}"; + + return type.Name; + } + + /// + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (operation == null) + throw new ArgumentNullException(nameof(operation)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + + operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}"; + + var authAttributes = context + .MethodInfo + .DeclaringType + .GetCustomAttributes(true) + .Union( + context + .MethodInfo + .GetCustomAttributes(true)) + .OfType(); + + if (authAttributes.Any()) + { + var tokenScheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = TokenSecuritySchemeId, + }, + }; + + operation.Security = new List + { + new OpenApiSecurityRequirement + { + { + tokenScheme, + new List() + }, + }, + }; + + if (typeof(InstanceRequiredController).IsAssignableFrom(context.MethodInfo.DeclaringType)) + operation.Parameters.Insert(0, new OpenApiParameter + { + Reference = new OpenApiReference + { + Type = ReferenceType.Parameter, + Id = ApiHeaders.InstanceIdHeader, + }, + }); + else if (typeof(TransferController).IsAssignableFrom(context.MethodInfo.DeclaringType)) + if (context.MethodInfo.Name == nameof(TransferController.Upload)) + operation.RequestBody = new OpenApiRequestBody + { + Content = new Dictionary + { + { + MediaTypeNames.Application.Octet, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary", + }, + } + }, + }, + }; + else if (context.MethodInfo.Name == nameof(TransferController.Download)) + { + var twoHundredResponseContents = operation.Responses["200"].Content; + var fileContent = twoHundredResponseContents[MediaTypeNames.Application.Json]; + twoHundredResponseContents.Remove(MediaTypeNames.Application.Json); + twoHundredResponseContents.Add(MediaTypeNames.Application.Octet, fileContent); + } + } + else if (context.MethodInfo.Name == nameof(HomeController.CreateToken)) + { + var passwordScheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = PasswordSecuritySchemeId, + }, + }; + + var oAuthScheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = OAuthSecuritySchemeId, + }, + }; + + operation.Parameters.Add(new OpenApiParameter + { + In = ParameterLocation.Header, + Name = ApiHeaders.OAuthProviderHeader, + Description = "The external OAuth service provider.", + Style = ParameterStyle.Simple, + Example = new OpenApiString("Discord"), + Schema = new OpenApiSchema + { + Type = "string", + }, + }); + + operation.Security = new List + { + new OpenApiSecurityRequirement + { + { + passwordScheme, + new List() + }, + { + oAuthScheme, + new List() + }, + }, + }; + } + } + + /// + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + if (swaggerDoc == null) + throw new ArgumentNullException(nameof(swaggerDoc)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + + swaggerDoc.ExternalDocs = new OpenApiExternalDocs + { + Description = "API Usage Documentation", + Url = new Uri("https://tgstation.github.io/tgstation-server/api.html"), + }; + + swaggerDoc.Components.Parameters.Add(ApiHeaders.InstanceIdHeader, new OpenApiParameter + { + In = ParameterLocation.Header, + Name = ApiHeaders.InstanceIdHeader, + Description = "The instance ID being accessed", + Required = true, + Style = ParameterStyle.Simple, + Schema = new OpenApiSchema + { + Type = "integer", + }, + }); + + var productHeaderSchema = new OpenApiSchema + { + Type = "string", + Format = "productheader", + }; + + swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter + { + In = ParameterLocation.Header, + Name = ApiHeaders.ApiVersionHeader, + Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"", + Required = true, + Style = ParameterStyle.Simple, + Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"), + Schema = productHeaderSchema, + }); + + swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter + { + In = ParameterLocation.Header, + Name = HeaderNames.UserAgent, + Description = "The user agent of the calling client.", + Required = true, + Style = ParameterStyle.Simple, + Example = new OpenApiString("Your-user-agent/1.0.0.0"), + Schema = productHeaderSchema, + }); + + var allSchemas = context + .SchemaRepository + .Schemas; + foreach (var path in swaggerDoc.Paths) + foreach (var operation in path.Value.Operations.Select(x => x.Value)) + { + operation.Parameters.Insert(0, new OpenApiParameter + { + Reference = new OpenApiReference + { + Type = ReferenceType.Parameter, + Id = ApiHeaders.ApiVersionHeader, + }, + }); + + operation.Parameters.Insert(1, new OpenApiParameter + { + Reference = new OpenApiReference + { + Type = ReferenceType.Parameter, + Id = HeaderNames.UserAgent, + }, + }); + } + + AddDefaultResponses(swaggerDoc); + } + /// public void Apply(OpenApiSchema schema, SchemaFilterContext context) { diff --git a/src/Tgstation.Server.Host/Database/DatabaseCollection.cs b/src/Tgstation.Server.Host/Database/DatabaseCollection.cs index 5821cd0ef2..78e745390e 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseCollection.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseCollection.cs @@ -1,11 +1,12 @@ -using Microsoft.EntityFrameworkCore; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; +using Microsoft.EntityFrameworkCore; + namespace Tgstation.Server.Host.Database { /// @@ -17,7 +18,7 @@ namespace Tgstation.Server.Host.Database readonly DbSet dbSet; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public DatabaseCollection(DbSet dbSet) @@ -61,4 +62,4 @@ namespace Tgstation.Server.Host.Database /// IEnumerator IEnumerable.GetEnumerator() => dbSet.AsQueryable().GetEnumerator(); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs index 037418fbdc..2d4299e4fa 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs @@ -1,9 +1,11 @@ using System; using System.Data.Common; using System.Data.SqlClient; + using Microsoft.Data.Sqlite; using MySql.Data.MySqlClient; using Npgsql; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database @@ -23,22 +25,22 @@ namespace Tgstation.Server.Host.Database case DatabaseType.MySql: return new MySqlConnection { - ConnectionString = connectionString + ConnectionString = connectionString, }; case DatabaseType.SqlServer: return new SqlConnection { - ConnectionString = connectionString + ConnectionString = connectionString, }; case DatabaseType.Sqlite: return new SqliteConnection { - ConnectionString = connectionString + ConnectionString = connectionString, }; case DatabaseType.PostgresSql: return new NpgsqlConnection { - ConnectionString = connectionString + ConnectionString = connectionString, }; default: throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid DatabaseType!"); diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 819afa7af5..290caeb0d2 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -1,14 +1,16 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database.Migrations; using Tgstation.Server.Host.Models; @@ -16,7 +18,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Database { /// - /// Backend abstract implementation of + /// Backend abstract implementation of . /// #pragma warning disable CA1506 // TODO: Decomplexify public abstract class DatabaseContext : DbContext, IDatabaseContext @@ -82,35 +84,30 @@ namespace Tgstation.Server.Host.Database public DbSet ReattachInformations { get; set; } /// - /// The s in the + /// The s in the . /// public DbSet TestMerges { get; set; } /// - /// The s in the + /// The s in the . /// public DbSet RevInfoTestMerges { get; set; } /// - /// The s in the + /// The s in the . /// public DbSet OAuthConnections { get; set; } /// - /// The s in the + /// The s in the . /// public DbSet PermissionSets { get; set; } /// - /// The s in the + /// The s in the . /// public DbSet Groups { get; set; } - /// - /// The for the / foreign key. - /// - protected virtual DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.ClientNoAction; - /// IDatabaseCollection IDatabaseContext.Users => usersCollection; @@ -156,6 +153,11 @@ namespace Tgstation.Server.Host.Database /// IDatabaseCollection IDatabaseContext.PermissionSets => permissionSets; + /// + /// The for the / foreign key. + /// + protected virtual DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.ClientNoAction; + /// /// Backing field for . /// @@ -252,7 +254,7 @@ namespace Tgstation.Server.Host.Database } /// - /// Construct a + /// Initializes a new instance of the class. /// /// The for the . protected DatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) @@ -274,6 +276,33 @@ namespace Tgstation.Server.Host.Database permissionSets = new DatabaseCollection(PermissionSets); } + /// + public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); + + /// + public Task Drop(CancellationToken cancellationToken) => Database.EnsureDeletedAsync(cancellationToken); + + /// + public async Task Migrate(ILogger logger, CancellationToken cancellationToken) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); + var wasEmpty = !migrations.Any(); + + if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any()) + { + logger.LogInformation("Migrating database..."); + await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); + } + else + logger.LogDebug("No migrations to apply"); + + wasEmpty |= (await Users.AsQueryable().CountAsync(cancellationToken).ConfigureAwait(false)) == 0; + + return wasEmpty; + } + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -342,33 +371,6 @@ namespace Tgstation.Server.Host.Database instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); } - /// - public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); - - /// - public Task Drop(CancellationToken cancellationToken) => Database.EnsureDeletedAsync(cancellationToken); - - /// - public async Task Migrate(ILogger logger, CancellationToken cancellationToken) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); - var wasEmpty = !migrations.Any(); - - if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any()) - { - logger.LogInformation("Migrating database..."); - await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); - } - else - logger.LogDebug("No migrations to apply"); - - wasEmpty |= (await Users.AsQueryable().CountAsync(cancellationToken).ConfigureAwait(false)) == 0; - - return wasEmpty; - } - // HEY YOU // IF YOU HAVE A TEST THAT'S CREATING ERRORS BECAUSE THESE VALUES AREN'T SET CORRECTLY THERE'S MORE TO FIXING IT THAN JUST UPDATING THEM // IN THE FUNCTION BELOW YOU ALSO NEED TO CORRECTLY SET THE RIGHT MIGRATION TO DOWNLOAD TO FOR THE LAST TGS VERSION @@ -402,7 +404,7 @@ namespace Tgstation.Server.Host.Database DatabaseType currentDatabaseType, CancellationToken cancellationToken) { - if(logger == null) + if (logger == null) throw new ArgumentNullException(nameof(logger)); if (targetVersion == null) throw new ArgumentNullException(nameof(targetVersion)); @@ -486,7 +488,7 @@ namespace Tgstation.Server.Host.Database // already setup var migrationSubstitution = currentDatabaseType switch { - DatabaseType.SqlServer => null,// already setup + DatabaseType.SqlServer => null, // already setup DatabaseType.MySql => "MY{0}", DatabaseType.Sqlite => "SL{0}", DatabaseType.PostgresSql => "PG{0}", diff --git a/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs index 281ca90f59..63865df090 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs @@ -1,21 +1,22 @@ -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + namespace Tgstation.Server.Host.Database { /// sealed class DatabaseContextFactory : IDatabaseContextFactory { /// - /// The for the + /// The for the . /// readonly IServiceScopeFactory scopeFactory; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of . Created scopes must be able to provide instances of + /// The value of . Created scopes must be able to provide instances of . public DatabaseContextFactory(IServiceScopeFactory scopeFactory) { this.scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); diff --git a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs index 26e24c27e4..03d3783abb 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs @@ -1,16 +1,19 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.System; + using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Database @@ -19,7 +22,7 @@ namespace Tgstation.Server.Host.Database sealed class DatabaseSeeder : IDatabaseSeeder { /// - /// The for the + /// The for the . /// readonly ICryptographySuite cryptographySuite; @@ -49,34 +52,9 @@ namespace Tgstation.Server.Host.Database readonly DatabaseConfiguration databaseConfiguration; /// - /// Construct a + /// Add a default system to a given . /// - /// The value of - /// The value of . - /// The containing the value of . - /// The containing the value of . - /// The value of - /// The value of . - public DatabaseSeeder( - ICryptographySuite cryptographySuite, - IPlatformIdentifier platformIdentifier, - IOptions generalConfigurationOptions, - IOptions databaseConfigurationOptions, - ILogger databaseLogger, - ILogger logger) - { - this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); - this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); - databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); - generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); - this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// Add a default system to a given - /// - /// The to add a system to + /// The to add a system to. /// An existing , if any. /// The created system . static User SeedSystemUser(IDatabaseContext databaseContext, User tgsUser = null) @@ -99,9 +77,79 @@ namespace Tgstation.Server.Host.Database } /// - /// Add a default admin to a given + /// Initializes a new instance of the class. /// - /// The to add an admin to + /// The value of . + /// The value of . + /// The containing the value of . + /// The containing the value of . + /// The value of . + /// The value of . + public DatabaseSeeder( + ICryptographySuite cryptographySuite, + IPlatformIdentifier platformIdentifier, + IOptions generalConfigurationOptions, + IOptions databaseConfigurationOptions, + ILogger databaseLogger, + ILogger logger) + { + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); + databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); + generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public async Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + if (databaseContext == null) + throw new ArgumentNullException(nameof(databaseContext)); + + if (databaseConfiguration.DropDatabase) + { + logger.LogCritical("DropDatabase configuration option set! Dropping any existing database..."); + await databaseContext.Drop(cancellationToken).ConfigureAwait(false); + } + + var wasEmpty = await databaseContext.Migrate(databaseLogger, cancellationToken).ConfigureAwait(false); + if (wasEmpty) + { + logger.LogInformation("Seeding database..."); + await SeedDatabase(databaseContext, cancellationToken).ConfigureAwait(false); + } + else + { + if (databaseConfiguration.ResetAdminPassword) + { + logger.LogWarning("Enabling and resetting admin password due to configuration!"); + await ResetAdminPassword(databaseContext, cancellationToken).ConfigureAwait(false); + } + + await SanitizeDatabase(databaseContext, cancellationToken).ConfigureAwait(false); + } + } + + /// + public Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken) + { + if (databaseContext == null) + throw new ArgumentNullException(nameof(databaseContext)); + if (downgradeVersion == null) + throw new ArgumentNullException(nameof(downgradeVersion)); + + return databaseContext.SchemaDowngradeForServerVersion( + databaseLogger, + downgradeVersion, + databaseConfiguration.DatabaseType, + cancellationToken); + } + + /// + /// Add a default admin to a given . + /// + /// The to add an admin to. /// The created admin . User SeedAdminUser(IDatabaseContext databaseContext) { @@ -123,11 +171,11 @@ namespace Tgstation.Server.Host.Database } /// - /// Initially seed a given + /// Initially seed a given . /// - /// The to seed - /// The for the operation - /// A representing the running operation + /// The to seed. + /// The for the operation. + /// A representing the running operation. async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) { var adminUser = SeedAdminUser(databaseContext); @@ -206,7 +254,7 @@ namespace Tgstation.Server.Host.Database { var newDDSettings = new DreamDaemonSettings { - Id = id + Id = id, }; databaseContext.DreamDaemonSettings.Attach(newDDSettings); @@ -226,9 +274,9 @@ namespace Tgstation.Server.Host.Database /// /// Changes the admin password in back to it's default, enables the account, and gives it access. /// - /// The to reset the admin password for - /// The for the operation - /// A representing the running operation + /// The to reset the admin password for. + /// The for the operation. + /// A representing the running operation. async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken) { var admin = await GetAdminUser(databaseContext, cancellationToken).ConfigureAwait(false); @@ -244,7 +292,7 @@ namespace Tgstation.Server.Host.Database admin.PermissionSet = new PermissionSet { InstanceManagerRights = InstanceManagerRights.None, - AdministrationRights = AdministrationRights.WriteUsers + AdministrationRights = AdministrationRights.WriteUsers, }; } else @@ -277,50 +325,5 @@ namespace Tgstation.Server.Host.Database return admin; } - - /// - public async Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken) - { - if (databaseContext == null) - throw new ArgumentNullException(nameof(databaseContext)); - - if (databaseConfiguration.DropDatabase) - { - logger.LogCritical("DropDatabase configuration option set! Dropping any existing database..."); - await databaseContext.Drop(cancellationToken).ConfigureAwait(false); - } - - var wasEmpty = await databaseContext.Migrate(databaseLogger, cancellationToken).ConfigureAwait(false); - if (wasEmpty) - { - logger.LogInformation("Seeding database..."); - await SeedDatabase(databaseContext, cancellationToken).ConfigureAwait(false); - } - else - { - if (databaseConfiguration.ResetAdminPassword) - { - logger.LogWarning("Enabling and resetting admin password due to configuration!"); - await ResetAdminPassword(databaseContext, cancellationToken).ConfigureAwait(false); - } - - await SanitizeDatabase(databaseContext, cancellationToken).ConfigureAwait(false); - } - } - - /// - public Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken) - { - if (databaseContext == null) - throw new ArgumentNullException(nameof(databaseContext)); - if (downgradeVersion == null) - throw new ArgumentNullException(nameof(downgradeVersion)); - - return databaseContext.SchemaDowngradeForServerVersion( - databaseLogger, - downgradeVersion, - databaseConfiguration.DatabaseType, - cancellationToken); - } } } diff --git a/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs index 36da823aa4..a0569aafe3 100644 --- a/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs @@ -1,22 +1,23 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Design { /// - /// Contains helpers for creating design time s + /// Contains helpers for creating design time s. /// static class DesignTimeDbContextFactoryHelpers { /// - /// Get the for the + /// Get the for the . /// /// The to create for. /// The . /// The . /// The . - /// The for the + /// The for the . public static DbContextOptions CreateDatabaseContextOptions( DatabaseType databaseType, string connectionString, @@ -28,7 +29,7 @@ namespace Tgstation.Server.Host.Database.Design DesignTime = true, DatabaseType = databaseType, ConnectionString = connectionString, - ServerVersion = serverVersion + ServerVersion = serverVersion, }; var optionsFac = new DbContextOptionsBuilder(); diff --git a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs index 448e3f3405..cf758ef363 100644 --- a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore.Design; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Design diff --git a/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs index 3cd399f919..b37e7b66a7 100644 --- a/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore.Design; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Design diff --git a/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs index ba12ce48ff..bf137fcf4b 100644 --- a/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore.Design; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Design diff --git a/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs index 5344c0f911..b855f8ad9c 100644 --- a/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore.Design; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Design diff --git a/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs b/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs index 9ebe790587..abea5f1702 100644 --- a/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs @@ -1,4 +1,5 @@ using System.Data.Common; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database @@ -9,11 +10,11 @@ namespace Tgstation.Server.Host.Database interface IDatabaseConnectionFactory { /// - /// Create a + /// Create a . /// - /// The - /// The to create - /// A new + /// The . + /// The to create. + /// A new . DbConnection CreateConnection(string connectionString, DatabaseType databaseType); } } diff --git a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs index ab122b7e89..f4468c63f2 100644 --- a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs @@ -1,8 +1,10 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; @@ -89,10 +91,10 @@ namespace Tgstation.Server.Host.Database IDatabaseCollection PermissionSets { get; } /// - /// Saves changes made to the + /// Saves changes made to the . /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Save(CancellationToken cancellationToken); /// @@ -103,21 +105,21 @@ namespace Tgstation.Server.Host.Database Task Drop(CancellationToken cancellationToken); /// - /// Creates and migrates the + /// Creates and migrates the . /// /// The to use. - /// The for the operation + /// The for the operation. /// A resulting in if the database should be seeded, otherwise. Task Migrate(ILogger logger, CancellationToken cancellationToken); /// - /// Attempt to downgrade the schema to the migration used for a given server + /// Attempt to downgrade the schema to the migration used for a given server . /// /// The to use. - /// The tgstation-server that the schema should downgrade for + /// The tgstation-server that the schema should downgrade for. /// The in use. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task SchemaDowngradeForServerVersion( ILogger logger, Version targetVersion, diff --git a/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs b/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs index 3a9c0f396a..7954066e0b 100644 --- a/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs @@ -4,15 +4,15 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Database { /// - /// Factory for scoping usage of s. Meant for use by + /// Factory for scoping usage of s. Meant for use by . /// public interface IDatabaseContextFactory { /// - /// Run an in the scope of an + /// Run an in the scope of an . /// - /// The operation to run - /// A representing the running + /// The operation to run. + /// A representing the running . Task UseContext(Func operation); } } diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180906135553_MSInitialCreate.cs b/src/Tgstation.Server.Host/Database/Migrations/20180906135553_MSInitialCreate.cs index 4b379f424d..d4e3002fff 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180906135553_MSInitialCreate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180906135553_MSInitialCreate.cs @@ -1,13 +1,14 @@ using System; + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// The initial database migration for MSSQL + /// The initial database migration for MSSQL. /// - #pragma warning disable CA1506 +#pragma warning disable CA1506 public partial class MSInitialCreate : Migration { /// @@ -31,7 +32,7 @@ namespace Tgstation.Server.Host.Database.Migrations PasswordHash = table.Column(nullable: true), CreatedById = table.Column(nullable: true), CanonicalName = table.Column(nullable: false), - LastPasswordUpdate = table.Column(nullable: true) + LastPasswordUpdate = table.Column(nullable: true), }, constraints: table => { @@ -56,7 +57,7 @@ namespace Tgstation.Server.Host.Database.Migrations Tag = table.Column(nullable: true), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - ChatSettingsId = table.Column(nullable: false) + ChatSettingsId = table.Column(nullable: false), }, constraints: table => { @@ -77,7 +78,7 @@ namespace Tgstation.Server.Host.Database.Migrations RebootState = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - CompileJobId = table.Column(nullable: true) + CompileJobId = table.Column(nullable: true), }, constraints: table => { @@ -92,7 +93,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), AlphaId = table.Column(nullable: true), - BravoId = table.Column(nullable: true) + BravoId = table.Column(nullable: true), }, constraints: table => { @@ -122,7 +123,7 @@ namespace Tgstation.Server.Host.Database.Migrations Online = table.Column(nullable: false), ConfigurationType = table.Column(nullable: false), AutoUpdateInterval = table.Column(nullable: false), - WatchdogReattachInformationId = table.Column(nullable: true) + WatchdogReattachInformationId = table.Column(nullable: true), }, constraints: table => { @@ -145,7 +146,7 @@ namespace Tgstation.Server.Host.Database.Migrations Enabled = table.Column(nullable: true), Provider = table.Column(nullable: true), ConnectionString = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -174,7 +175,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), ProcessId = table.Column(nullable: true), AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -195,7 +196,7 @@ namespace Tgstation.Server.Host.Database.Migrations ApiValidationPort = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -222,7 +223,7 @@ namespace Tgstation.Server.Host.Database.Migrations ConfigurationRights = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -256,7 +257,7 @@ namespace Tgstation.Server.Host.Database.Migrations CancelRight = table.Column(nullable: true), StartedById = table.Column(nullable: false), CancelledById = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -295,7 +296,7 @@ namespace Tgstation.Server.Host.Database.Migrations AutoUpdatesSynchronize = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -316,7 +317,7 @@ namespace Tgstation.Server.Host.Database.Migrations OriginCommitSha = table.Column(maxLength: 40, nullable: false), Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -340,7 +341,7 @@ namespace Tgstation.Server.Host.Database.Migrations DirectoryName = table.Column(nullable: true), JobId = table.Column(nullable: true), RevisionInformationId = table.Column(nullable: false), - ByondVersion = table.Column(nullable: false) + ByondVersion = table.Column(nullable: false), }, constraints: table => { @@ -374,7 +375,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), MergedAt = table.Column(nullable: false), MergedById = table.Column(nullable: false), - PrimaryRevisionInformationId = table.Column(nullable: true) + PrimaryRevisionInformationId = table.Column(nullable: true), }, constraints: table => { @@ -400,7 +401,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), TestMergeId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false) + RevisionInformationId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180906143029_MYInitialCreate.cs b/src/Tgstation.Server.Host/Database/Migrations/20180906143029_MYInitialCreate.cs index 0ad0bf0c33..0b2638a0d5 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180906143029_MYInitialCreate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180906143029_MYInitialCreate.cs @@ -1,13 +1,14 @@ using System; + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// The initial database migration for MySQL/MariaDB + /// The initial database migration for MySQL/MariaDB. /// - #pragma warning disable CA1506 +#pragma warning disable CA1506 public partial class MYInitialCreate : Migration { /// @@ -31,7 +32,7 @@ namespace Tgstation.Server.Host.Database.Migrations PasswordHash = table.Column(nullable: true), CreatedById = table.Column(nullable: true), CanonicalName = table.Column(nullable: false), - LastPasswordUpdate = table.Column(nullable: true) + LastPasswordUpdate = table.Column(nullable: true), }, constraints: table => { @@ -56,7 +57,7 @@ namespace Tgstation.Server.Host.Database.Migrations Tag = table.Column(nullable: true), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - ChatSettingsId = table.Column(nullable: false) + ChatSettingsId = table.Column(nullable: false), }, constraints: table => { @@ -77,7 +78,7 @@ namespace Tgstation.Server.Host.Database.Migrations RebootState = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - CompileJobId = table.Column(nullable: true) + CompileJobId = table.Column(nullable: true), }, constraints: table => { @@ -92,7 +93,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), AlphaId = table.Column(nullable: true), - BravoId = table.Column(nullable: true) + BravoId = table.Column(nullable: true), }, constraints: table => { @@ -122,7 +123,7 @@ namespace Tgstation.Server.Host.Database.Migrations Online = table.Column(nullable: false), ConfigurationType = table.Column(nullable: false), AutoUpdateInterval = table.Column(nullable: false), - WatchdogReattachInformationId = table.Column(nullable: true) + WatchdogReattachInformationId = table.Column(nullable: true), }, constraints: table => { @@ -145,7 +146,7 @@ namespace Tgstation.Server.Host.Database.Migrations Enabled = table.Column(nullable: true), Provider = table.Column(nullable: true), ConnectionString = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -174,7 +175,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), ProcessId = table.Column(nullable: true), AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -195,7 +196,7 @@ namespace Tgstation.Server.Host.Database.Migrations ApiValidationPort = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -222,7 +223,7 @@ namespace Tgstation.Server.Host.Database.Migrations ConfigurationRights = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -256,7 +257,7 @@ namespace Tgstation.Server.Host.Database.Migrations CancelRight = table.Column(nullable: true), StartedById = table.Column(nullable: false), CancelledById = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -295,7 +296,7 @@ namespace Tgstation.Server.Host.Database.Migrations AutoUpdatesSynchronize = table.Column(nullable: false), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -316,7 +317,7 @@ namespace Tgstation.Server.Host.Database.Migrations OriginCommitSha = table.Column(maxLength: 40, nullable: false), Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -340,7 +341,7 @@ namespace Tgstation.Server.Host.Database.Migrations DirectoryName = table.Column(nullable: true), JobId = table.Column(nullable: true), RevisionInformationId = table.Column(nullable: false), - ByondVersion = table.Column(nullable: false) + ByondVersion = table.Column(nullable: false), }, constraints: table => { @@ -374,7 +375,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), MergedAt = table.Column(nullable: false), MergedById = table.Column(nullable: false), - PrimaryRevisionInformationId = table.Column(nullable: true) + PrimaryRevisionInformationId = table.Column(nullable: true), }, constraints: table => { @@ -400,7 +401,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), TestMergeId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false) + RevisionInformationId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180918020726_MYAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Database/Migrations/20180918020726_MYAddMinimumSecurity.cs index a12577e3e2..1a34b44c9b 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180918020726_MYAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180918020726_MYAddMinimumSecurity.cs @@ -1,11 +1,13 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Add the and columns for MySQL/MariaDB + /// Add the and columns for MySQL/MariaDB. /// public partial class MYAddMinimumSecurity : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180918021228_MSAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Database/Migrations/20180918021228_MSAddMinimumSecurity.cs index 4d6316e70c..57d8541ed9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180918021228_MSAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180918021228_MSAddMinimumSecurity.cs @@ -1,11 +1,13 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Add the and columns for MSSQL + /// Add the and columns for MSSQL. /// public partial class MSAddMinimumSecurity : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs b/src/Tgstation.Server.Host/Database/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs index 02db35bd3f..e510a62925 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs @@ -1,10 +1,11 @@ using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Cleans up of nullable columns and foreign keys MySQL/MariaDB + /// Cleans up of nullable columns and foreign keys MySQL/MariaDB. /// public partial class MYNullableAndForeignKeyCleanup : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs b/src/Tgstation.Server.Host/Database/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs index 00b73953e6..a25ee2d2fc 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs @@ -1,10 +1,11 @@ using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Cleans up of nullable columns and foreign keys MSSQL + /// Cleans up of nullable columns and foreign keys MSSQL. /// public partial class MSNullableAndForeignKeyCleanup : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180926033145_MSReattachCompileJobRequired.cs b/src/Tgstation.Server.Host/Database/Migrations/20180926033145_MSReattachCompileJobRequired.cs index 205ffd2e10..d2bb7d4c33 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180926033145_MSReattachCompileJobRequired.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180926033145_MSReattachCompileJobRequired.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Marks the s of s as non-nullable for MSSQL + /// Marks the s of s as non-nullable for MSSQL. /// public partial class MSReattachCompileJobRequired : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20180926034014_MYReattachCompileJobRequired.cs b/src/Tgstation.Server.Host/Database/Migrations/20180926034014_MYReattachCompileJobRequired.cs index ae4de6c9a7..0d75aa9fc6 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20180926034014_MYReattachCompileJobRequired.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20180926034014_MYReattachCompileJobRequired.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Marks the s of s as non-nullable for MySQL/MariaDB + /// Marks the s of s as non-nullable for MySQL/MariaDB. /// public partial class MYReattachCompileJobRequired : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20181124231534_MSToggleTestmergeComments.cs b/src/Tgstation.Server.Host/Database/Migrations/20181124231534_MSToggleTestmergeComments.cs index 6d7f7d1b63..caa9334cb4 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20181124231534_MSToggleTestmergeComments.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20181124231534_MSToggleTestmergeComments.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Add the column for MSSQL + /// Add the column for MSSQL. /// public partial class MSToggleTestmergeComments : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20181124231549_MYToggleTestmergeComments.cs b/src/Tgstation.Server.Host/Database/Migrations/20181124231549_MYToggleTestmergeComments.cs index 5084606961..9290890501 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20181124231549_MYToggleTestmergeComments.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20181124231549_MYToggleTestmergeComments.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Add the column for MySQL/MariaDB + /// Add the column for MySQL/MariaDB. /// public partial class MYToggleTestmergeComments : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20190219041825_MSFixRevInfoIndex.cs b/src/Tgstation.Server.Host/Database/Migrations/20190219041825_MSFixRevInfoIndex.cs index 9fae2c0721..af0ad1e571 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20190219041825_MSFixRevInfoIndex.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20190219041825_MSFixRevInfoIndex.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { /// - /// Make commit shas non-unique per Instance for MSSQL + /// Make commit shas non-unique per Instance for MSSQL. /// public partial class MSFixRevInfoIndex : Migration { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20190219042440_MYFixRevInfoIndex.cs b/src/Tgstation.Server.Host/Database/Migrations/20190219042440_MYFixRevInfoIndex.cs index df1c0e9e1e..b0ae958180 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20190219042440_MYFixRevInfoIndex.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20190219042440_MYFixRevInfoIndex.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200121170252_MSAddChatBotReconnectionInterval.cs b/src/Tgstation.Server.Host/Database/Migrations/20200121170252_MSAddChatBotReconnectionInterval.cs index 47e4b74c0b..73605646d3 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200121170252_MSAddChatBotReconnectionInterval.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200121170252_MSAddChatBotReconnectionInterval.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200121171159_MYAddChatBotReconnectionInterval.cs b/src/Tgstation.Server.Host/Database/Migrations/20200121171159_MYAddChatBotReconnectionInterval.cs index a8dce31746..d5280c4521 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200121171159_MYAddChatBotReconnectionInterval.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200121171159_MYAddChatBotReconnectionInterval.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200411164808_MSTonsOfValidation.cs b/src/Tgstation.Server.Host/Database/Migrations/20200411164808_MSTonsOfValidation.cs index b55e81288e..904e6684a4 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200411164808_MSTonsOfValidation.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200411164808_MSTonsOfValidation.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200411170010_MYTonsOfValidation.cs b/src/Tgstation.Server.Host/Database/Migrations/20200411170010_MYTonsOfValidation.cs index d9226554ec..bce2468d95 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200411170010_MYTonsOfValidation.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200411170010_MYTonsOfValidation.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200420175359_MSLimitsOnChat.cs b/src/Tgstation.Server.Host/Database/Migrations/20200420175359_MSLimitsOnChat.cs index 9674cf0200..560681d834 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200420175359_MSLimitsOnChat.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200420175359_MSLimitsOnChat.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Database.Migrations diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200420181015_MYLimitsOnChat.cs b/src/Tgstation.Server.Host/Database/Migrations/20200420181015_MYLimitsOnChat.cs index b54bcd8f2c..e1c2828f11 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200420181015_MYLimitsOnChat.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200420181015_MYLimitsOnChat.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Database.Migrations diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200422001202_MSAddCompileJobDMApiVersion.cs b/src/Tgstation.Server.Host/Database/Migrations/20200422001202_MSAddCompileJobDMApiVersion.cs index 68b60c3240..f4c1bcc7cd 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200422001202_MSAddCompileJobDMApiVersion.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200422001202_MSAddCompileJobDMApiVersion.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200422010115_MYAddCompileJobDMApiVersion.cs b/src/Tgstation.Server.Host/Database/Migrations/20200422010115_MYAddCompileJobDMApiVersion.cs index cb14d3256e..63126d4b00 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200422010115_MYAddCompileJobDMApiVersion.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200422010115_MYAddCompileJobDMApiVersion.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200423052142_MSKillJsonsAddDMApiVersion.cs b/src/Tgstation.Server.Host/Database/Migrations/20200423052142_MSKillJsonsAddDMApiVersion.cs index 2881f585b5..d419b4d6de 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200423052142_MSKillJsonsAddDMApiVersion.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200423052142_MSKillJsonsAddDMApiVersion.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200423052709_MYKillJsonsAddDMApiVersion.cs b/src/Tgstation.Server.Host/Database/Migrations/20200423052709_MYKillJsonsAddDMApiVersion.cs index 32e03425c8..42c4087674 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200423052709_MYKillJsonsAddDMApiVersion.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200423052709_MYKillJsonsAddDMApiVersion.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200425024433_MSAddJobErrorCodes.cs b/src/Tgstation.Server.Host/Database/Migrations/20200425024433_MSAddJobErrorCodes.cs index 35ff6adeaa..9a672617ee 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200425024433_MSAddJobErrorCodes.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200425024433_MSAddJobErrorCodes.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200425024953_MYAddJobErrorCodes.cs b/src/Tgstation.Server.Host/Database/Migrations/20200425024953_MYAddJobErrorCodes.cs index 928f23da07..8ad2ccc82d 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200425024953_MYAddJobErrorCodes.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200425024953_MYAddJobErrorCodes.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200427173852_MSFixCascadingDelete.cs b/src/Tgstation.Server.Host/Database/Migrations/20200427173852_MSFixCascadingDelete.cs index b8d845e7d0..8e4e647199 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200427173852_MSFixCascadingDelete.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200427173852_MSFixCascadingDelete.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200427180159_MYFixCascadingDelete.cs b/src/Tgstation.Server.Host/Database/Migrations/20200427180159_MYFixCascadingDelete.cs index 2a293c4477..f99058c010 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200427180159_MYFixCascadingDelete.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200427180159_MYFixCascadingDelete.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200427180904_SLRebuild.cs b/src/Tgstation.Server.Host/Database/Migrations/20200427180904_SLRebuild.cs index 2ee5bafeb4..29424cbb35 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200427180904_SLRebuild.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200427180904_SLRebuild.cs @@ -1,4 +1,5 @@ using System; + using Microsoft.EntityFrameworkCore.Migrations; #pragma warning disable CA1506 @@ -27,7 +28,7 @@ namespace Tgstation.Server.Host.Database.Migrations Online = table.Column(nullable: false), ConfigurationType = table.Column(nullable: false), AutoUpdateInterval = table.Column(nullable: false), - ChatBotLimit = table.Column(nullable: false) + ChatBotLimit = table.Column(nullable: false), }, constraints: table => { @@ -49,7 +50,7 @@ namespace Tgstation.Server.Host.Database.Migrations PasswordHash = table.Column(nullable: true), CreatedById = table.Column(nullable: true), CanonicalName = table.Column(nullable: false), - LastPasswordUpdate = table.Column(nullable: true) + LastPasswordUpdate = table.Column(nullable: true), }, constraints: table => { @@ -74,7 +75,7 @@ namespace Tgstation.Server.Host.Database.Migrations ChannelLimit = table.Column(nullable: false), Provider = table.Column(nullable: false), ConnectionString = table.Column(maxLength: 10000, nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -103,7 +104,7 @@ namespace Tgstation.Server.Host.Database.Migrations SoftShutdown = table.Column(nullable: false), ProcessId = table.Column(nullable: true), AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -125,7 +126,7 @@ namespace Tgstation.Server.Host.Database.Migrations ProjectName = table.Column(maxLength: 10000, nullable: true), ApiValidationPort = table.Column(nullable: false), ApiValidationSecurityLevel = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -153,7 +154,7 @@ namespace Tgstation.Server.Host.Database.Migrations AutoUpdatesKeepTestMerges = table.Column(nullable: false), AutoUpdatesSynchronize = table.Column(nullable: false), PostTestMergeComment = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -174,7 +175,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("Sqlite:Autoincrement", true), CommitSha = table.Column(maxLength: 40, nullable: false), OriginCommitSha = table.Column(maxLength: 40, nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -201,7 +202,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -236,7 +237,7 @@ namespace Tgstation.Server.Host.Database.Migrations CancelRight = table.Column(nullable: true), StartedById = table.Column(nullable: false), CancelledById = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -273,7 +274,7 @@ namespace Tgstation.Server.Host.Database.Migrations IsWatchdogChannel = table.Column(nullable: false), IsUpdatesChannel = table.Column(nullable: false), Tag = table.Column(maxLength: 10000, nullable: true), - ChatSettingsId = table.Column(nullable: false) + ChatSettingsId = table.Column(nullable: false), }, constraints: table => { @@ -301,7 +302,7 @@ namespace Tgstation.Server.Host.Database.Migrations Author = table.Column(nullable: false), MergedAt = table.Column(nullable: false), MergedById = table.Column(nullable: false), - PrimaryRevisionInformationId = table.Column(nullable: false) + PrimaryRevisionInformationId = table.Column(nullable: false), }, constraints: table => { @@ -335,7 +336,7 @@ namespace Tgstation.Server.Host.Database.Migrations ByondVersion = table.Column(nullable: false), DMApiMajorVersion = table.Column(nullable: true), DMApiMinorVersion = table.Column(nullable: true), - DMApiPatchVersion = table.Column(nullable: true) + DMApiPatchVersion = table.Column(nullable: true), }, constraints: table => { @@ -360,7 +361,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("Sqlite:Autoincrement", true), TestMergeId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false) + RevisionInformationId = table.Column(nullable: false), }, constraints: table => { @@ -390,7 +391,7 @@ namespace Tgstation.Server.Host.Database.Migrations Port = table.Column(nullable: false), RebootState = table.Column(nullable: false), LaunchSecurityLevel = table.Column(nullable: false), - CompileJobId = table.Column(nullable: false) + CompileJobId = table.Column(nullable: false), }, constraints: table => { @@ -412,7 +413,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaIsActive = table.Column(nullable: false), InstanceId = table.Column(nullable: false), AlphaId = table.Column(nullable: true), - BravoId = table.Column(nullable: true) + BravoId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200511005957_MSAddHeartbeat.cs b/src/Tgstation.Server.Host/Database/Migrations/20200511005957_MSAddHeartbeat.cs index 5e2aec847b..91e0288a4f 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200511005957_MSAddHeartbeat.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200511005957_MSAddHeartbeat.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200511011048_MYAddHeartbeat.cs b/src/Tgstation.Server.Host/Database/Migrations/20200511011048_MYAddHeartbeat.cs index aa6da59901..4a4c67074c 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200511011048_MYAddHeartbeat.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200511011048_MYAddHeartbeat.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200511012117_SLAddHeartbeat.cs b/src/Tgstation.Server.Host/Database/Migrations/20200511012117_SLAddHeartbeat.cs index 77fc1f8ad4..fe03be2ab9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200511012117_SLAddHeartbeat.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200511012117_SLAddHeartbeat.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { @@ -47,7 +48,7 @@ namespace Tgstation.Server.Host.Database.Migrations SoftShutdown = table.Column(nullable: false), ProcessId = table.Column(nullable: true), AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200511162912_SLRemoveSoftColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200511162912_SLRemoveSoftColumns.cs index 47e7f0a36c..91a5f08dc5 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200511162912_SLRemoveSoftColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200511162912_SLRemoveSoftColumns.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { @@ -31,7 +32,7 @@ namespace Tgstation.Server.Host.Database.Migrations StartupTimeout = table.Column(nullable: false), HeartbeatSeconds = table.Column(nullable: false), AutoStart = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200512181801_MSRemoveSoftColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200512181801_MSRemoveSoftColumns.cs index 06f1516fd5..4aa9405537 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200512181801_MSRemoveSoftColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200512181801_MSRemoveSoftColumns.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200512182535_MYRemoveSoftColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200512182535_MYRemoveSoftColumns.cs index d68e1d1e14..98167ac4dd 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200512182535_MYRemoveSoftColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200512182535_MYRemoveSoftColumns.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs index 10c42cce5d..66e77f48c4 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs @@ -1,4 +1,5 @@ using System; + using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; @@ -26,7 +27,7 @@ namespace Tgstation.Server.Host.Database.Migrations Online = table.Column(nullable: false), ConfigurationType = table.Column(nullable: false), AutoUpdateInterval = table.Column(nullable: false), - ChatBotLimit = table.Column(nullable: false) + ChatBotLimit = table.Column(nullable: false), }, constraints: table => { @@ -48,7 +49,7 @@ namespace Tgstation.Server.Host.Database.Migrations PasswordHash = table.Column(nullable: true), CreatedById = table.Column(nullable: true), CanonicalName = table.Column(nullable: false), - LastPasswordUpdate = table.Column(nullable: true) + LastPasswordUpdate = table.Column(nullable: true), }, constraints: table => { @@ -73,7 +74,7 @@ namespace Tgstation.Server.Host.Database.Migrations ChannelLimit = table.Column(nullable: false), Provider = table.Column(nullable: false), ConnectionString = table.Column(maxLength: 10000, nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -99,7 +100,7 @@ namespace Tgstation.Server.Host.Database.Migrations StartupTimeout = table.Column(nullable: false), HeartbeatSeconds = table.Column(nullable: false), AutoStart = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -121,7 +122,7 @@ namespace Tgstation.Server.Host.Database.Migrations ProjectName = table.Column(maxLength: 10000, nullable: true), ApiValidationPort = table.Column(nullable: false), ApiValidationSecurityLevel = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -149,7 +150,7 @@ namespace Tgstation.Server.Host.Database.Migrations AutoUpdatesKeepTestMerges = table.Column(nullable: false), AutoUpdatesSynchronize = table.Column(nullable: false), PostTestMergeComment = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -170,7 +171,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), CommitSha = table.Column(maxLength: 40, nullable: false), OriginCommitSha = table.Column(maxLength: 40, nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -197,7 +198,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -232,7 +233,7 @@ namespace Tgstation.Server.Host.Database.Migrations CancelRight = table.Column(nullable: true), StartedById = table.Column(nullable: false), CancelledById = table.Column(nullable: true), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -269,7 +270,7 @@ namespace Tgstation.Server.Host.Database.Migrations IsWatchdogChannel = table.Column(nullable: false), IsUpdatesChannel = table.Column(nullable: false), Tag = table.Column(maxLength: 10000, nullable: true), - ChatSettingsId = table.Column(nullable: false) + ChatSettingsId = table.Column(nullable: false), }, constraints: table => { @@ -297,7 +298,7 @@ namespace Tgstation.Server.Host.Database.Migrations Author = table.Column(nullable: false), MergedAt = table.Column(nullable: false), MergedById = table.Column(nullable: false), - PrimaryRevisionInformationId = table.Column(nullable: false) + PrimaryRevisionInformationId = table.Column(nullable: false), }, constraints: table => { @@ -331,7 +332,7 @@ namespace Tgstation.Server.Host.Database.Migrations ByondVersion = table.Column(nullable: false), DMApiMajorVersion = table.Column(nullable: true), DMApiMinorVersion = table.Column(nullable: true), - DMApiPatchVersion = table.Column(nullable: true) + DMApiPatchVersion = table.Column(nullable: true), }, constraints: table => { @@ -357,7 +358,7 @@ namespace Tgstation.Server.Host.Database.Migrations Id = table.Column(nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), TestMergeId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false) + RevisionInformationId = table.Column(nullable: false), }, constraints: table => { @@ -387,7 +388,7 @@ namespace Tgstation.Server.Host.Database.Migrations Port = table.Column(nullable: false), RebootState = table.Column(nullable: false), LaunchSecurityLevel = table.Column(nullable: false), - CompileJobId = table.Column(nullable: false) + CompileJobId = table.Column(nullable: false), }, constraints: table => { @@ -409,7 +410,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaIsActive = table.Column(nullable: false), InstanceId = table.Column(nullable: false), AlphaId = table.Column(nullable: true), - BravoId = table.Column(nullable: true) + BravoId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs index 3f2e2c658f..73f81043cd 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200616175424_MSTopicTimeout.cs b/src/Tgstation.Server.Host/Database/Migrations/20200616175424_MSTopicTimeout.cs index fd28951592..c36ed80596 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200616175424_MSTopicTimeout.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200616175424_MSTopicTimeout.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200616175535_MYTopicTimeout.cs b/src/Tgstation.Server.Host/Database/Migrations/20200616175535_MYTopicTimeout.cs index 2a850ed9d0..8a7823712a 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200616175535_MYTopicTimeout.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200616175535_MYTopicTimeout.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200616180742_SLTopicTimeout.cs b/src/Tgstation.Server.Host/Database/Migrations/20200616180742_SLTopicTimeout.cs index fcdcdf9500..f6cb755644 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200616180742_SLTopicTimeout.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200616180742_SLTopicTimeout.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { @@ -43,7 +44,7 @@ namespace Tgstation.Server.Host.Database.Migrations StartupTimeout = table.Column(nullable: false), AutoStart = table.Column(nullable: false), InstanceId = table.Column(nullable: false), - HeartbeatSeconds = table.Column(nullable: false) + HeartbeatSeconds = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200616180842_PGTopicTimeout.cs b/src/Tgstation.Server.Host/Database/Migrations/20200616180842_PGTopicTimeout.cs index 0ecc9843b2..c340ab68ee 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200616180842_PGTopicTimeout.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200616180842_PGTopicTimeout.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200629184803_MSRemoveExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Database/Migrations/20200629184803_MSRemoveExperimentalWatchdog.cs index 80625db4ea..a2803dca9a 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200629184803_MSRemoveExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200629184803_MSRemoveExperimentalWatchdog.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { @@ -65,7 +66,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaId = table.Column(type: "bigint", nullable: true), AlphaIsActive = table.Column(type: "bit", nullable: false), BravoId = table.Column(type: "bigint", nullable: true), - InstanceId = table.Column(type: "bigint", nullable: false) + InstanceId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200629184843_MYRemoveExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Database/Migrations/20200629184843_MYRemoveExperimentalWatchdog.cs index eae8c0c185..0412818df9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200629184843_MYRemoveExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200629184843_MYRemoveExperimentalWatchdog.cs @@ -1,6 +1,7 @@ -using Microsoft.EntityFrameworkCore.Metadata; +using System; + +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -66,7 +67,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaId = table.Column(type: "bigint", nullable: true), AlphaIsActive = table.Column(type: "tinyint(1)", nullable: false), BravoId = table.Column(type: "bigint", nullable: true), - InstanceId = table.Column(type: "bigint", nullable: false) + InstanceId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200629184928_PGRemoveExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Database/Migrations/20200629184928_PGRemoveExperimentalWatchdog.cs index 882ed08b62..a8055fd520 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200629184928_PGRemoveExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200629184928_PGRemoveExperimentalWatchdog.cs @@ -1,6 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -66,7 +67,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaId = table.Column(type: "bigint", nullable: true), AlphaIsActive = table.Column(type: "boolean", nullable: false), BravoId = table.Column(type: "bigint", nullable: true), - InstanceId = table.Column(type: "bigint", nullable: false) + InstanceId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200629185014_SLRemoveExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Database/Migrations/20200629185014_SLRemoveExperimentalWatchdog.cs index 3c62356589..00ea3bdca4 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200629185014_SLRemoveExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200629185014_SLRemoveExperimentalWatchdog.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { @@ -32,7 +33,7 @@ namespace Tgstation.Server.Host.Database.Migrations Port = table.Column(nullable: false), RebootState = table.Column(nullable: false), LaunchSecurityLevel = table.Column(nullable: false), - CompileJobId = table.Column(nullable: false) + CompileJobId = table.Column(nullable: false), }, constraints: table => { @@ -68,7 +69,7 @@ namespace Tgstation.Server.Host.Database.Migrations HeartbeatSeconds = table.Column(nullable: false), AutoStart = table.Column(nullable: false), InstanceId = table.Column(nullable: false), - TopicRequestTimeout = table.Column(nullable: false) + TopicRequestTimeout = table.Column(nullable: false), }, constraints: table => { @@ -122,7 +123,7 @@ namespace Tgstation.Server.Host.Database.Migrations AlphaId = table.Column(type: "INTEGER", nullable: true), AlphaIsActive = table.Column(type: "INTEGER", nullable: false), BravoId = table.Column(type: "INTEGER", nullable: true), - InstanceId = table.Column(type: "INTEGER", nullable: false) + InstanceId = table.Column(type: "INTEGER", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs index ee823e5fb3..0ccc774fcc 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs index cd189a8fbc..67a0816521 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.cs index 2488d21566..da71528e81 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163700_SLAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163700_SLAllowNullDMApi.cs index e001cc523f..7fa20fb8df 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200705163700_SLAllowNullDMApi.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163700_SLAllowNullDMApi.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Database.Migrations @@ -40,7 +42,7 @@ namespace Tgstation.Server.Host.Database.Migrations ByondVersion = table.Column(nullable: false), DMApiMajorVersion = table.Column(nullable: true), DMApiMinorVersion = table.Column(nullable: true), - DMApiPatchVersion = table.Column(nullable: true) + DMApiPatchVersion = table.Column(nullable: true), }, constraints: table => { @@ -99,7 +101,7 @@ namespace Tgstation.Server.Host.Database.Migrations ByondVersion = table.Column(nullable: false), DMApiMajorVersion = table.Column(nullable: true), DMApiMinorVersion = table.Column(nullable: true), - DMApiPatchVersion = table.Column(nullable: true) + DMApiPatchVersion = table.Column(nullable: true), }, constraints: table => { @@ -144,7 +146,7 @@ namespace Tgstation.Server.Host.Database.Migrations ProjectName = table.Column(maxLength: 10000, nullable: true), ApiValidationPort = table.Column(nullable: false), ApiValidationSecurityLevel = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200807213255_MSAddDeploymentColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200807213255_MSAddDeploymentColumns.cs index 2785f95372..c6199ef0d9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200807213255_MSAddDeploymentColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200807213255_MSAddDeploymentColumns.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200807213742_MYAddDeploymentColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200807213742_MYAddDeploymentColumns.cs index 7ec6918437..29e61f3f4e 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200807213742_MYAddDeploymentColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200807213742_MYAddDeploymentColumns.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200807214330_PGAddDeploymentColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200807214330_PGAddDeploymentColumns.cs index 41537d05de..3662c7d8f4 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200807214330_PGAddDeploymentColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200807214330_PGAddDeploymentColumns.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200807214754_SLAddDeploymentColumns.cs b/src/Tgstation.Server.Host/Database/Migrations/20200807214754_SLAddDeploymentColumns.cs index 0635951093..cac8ffab88 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200807214754_SLAddDeploymentColumns.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200807214754_SLAddDeploymentColumns.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -56,7 +57,7 @@ namespace Tgstation.Server.Host.Database.Migrations AutoUpdatesKeepTestMerges = table.Column(nullable: false), AutoUpdatesSynchronize = table.Column(nullable: false), PostTestMergeComment = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -94,7 +95,7 @@ namespace Tgstation.Server.Host.Database.Migrations ByondVersion = table.Column(nullable: false), DMApiMajorVersion = table.Column(nullable: true), DMApiMinorVersion = table.Column(nullable: true), - DMApiPatchVersion = table.Column(nullable: true) + DMApiPatchVersion = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201106082502_MSAddAdditionalDDParameters.cs b/src/Tgstation.Server.Host/Database/Migrations/20201106082502_MSAddAdditionalDDParameters.cs index 7829827d88..538fc0bbe9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201106082502_MSAddAdditionalDDParameters.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201106082502_MSAddAdditionalDDParameters.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201106082556_MYAddAdditionalDDParameters.cs b/src/Tgstation.Server.Host/Database/Migrations/20201106082556_MYAddAdditionalDDParameters.cs index af41641713..41a1453aea 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201106082556_MYAddAdditionalDDParameters.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201106082556_MYAddAdditionalDDParameters.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201106082653_PGAddAdditionalDDParameters.cs b/src/Tgstation.Server.Host/Database/Migrations/20201106082653_PGAddAdditionalDDParameters.cs index 6ff24c95c1..8b16888279 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201106082653_PGAddAdditionalDDParameters.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201106082653_PGAddAdditionalDDParameters.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201106082741_SLAddAdditionalDDParameters.cs b/src/Tgstation.Server.Host/Database/Migrations/20201106082741_SLAddAdditionalDDParameters.cs index c7b774018f..3230ec43eb 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201106082741_SLAddAdditionalDDParameters.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201106082741_SLAddAdditionalDDParameters.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -45,7 +46,7 @@ namespace Tgstation.Server.Host.Database.Migrations HeartbeatSeconds = table.Column(nullable: false), AutoStart = table.Column(nullable: false), InstanceId = table.Column(nullable: false), - TopicRequestTimeout = table.Column(nullable: false) + TopicRequestTimeout = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201122231219_MSAddOAuthConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20201122231219_MSAddOAuthConnections.cs index f965ae37de..1d0cae4f64 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201122231219_MSAddOAuthConnections.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201122231219_MSAddOAuthConnections.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -22,7 +23,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("SqlServer:Identity", "1, 1"), Provider = table.Column(nullable: false), ExternalUserId = table.Column(nullable: false, maxLength: 100), - UserId = table.Column(nullable: true) + UserId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201122231327_MYAddOAuthConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20201122231327_MYAddOAuthConnections.cs index d2741b785b..88a4df4e18 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201122231327_MYAddOAuthConnections.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201122231327_MYAddOAuthConnections.cs @@ -1,6 +1,7 @@ +using System; + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -23,7 +24,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), Provider = table.Column(nullable: false), ExternalUserId = table.Column(nullable: false, maxLength: 100), - UserId = table.Column(nullable: true) + UserId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201122231443_PGAddOAuthConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20201122231443_PGAddOAuthConnections.cs index b660fe83e0..2ad4c50a12 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201122231443_PGAddOAuthConnections.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201122231443_PGAddOAuthConnections.cs @@ -1,6 +1,7 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -23,7 +24,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Provider = table.Column(nullable: false), ExternalUserId = table.Column(nullable: false, maxLength: 100), - UserId = table.Column(nullable: true) + UserId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201122231546_SLAddOAuthConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20201122231546_SLAddOAuthConnections.cs index 9c8cdfb567..e9ef148ac8 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201122231546_SLAddOAuthConnections.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201122231546_SLAddOAuthConnections.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -22,7 +23,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("Sqlite:Autoincrement", true), Provider = table.Column(nullable: false), ExternalUserId = table.Column(nullable: false, maxLength: 100), - UserId = table.Column(nullable: true) + UserId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201209194250_MSGenericTestMergingUpdate.cs b/src/Tgstation.Server.Host/Database/Migrations/20201209194250_MSGenericTestMergingUpdate.cs index 00413a1d03..be16bbe72b 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201209194250_MSGenericTestMergingUpdate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201209194250_MSGenericTestMergingUpdate.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201209194348_MYGenericTestMergingUpdate.cs b/src/Tgstation.Server.Host/Database/Migrations/20201209194348_MYGenericTestMergingUpdate.cs index d8cf64d164..63d4e18cb5 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201209194348_MYGenericTestMergingUpdate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201209194348_MYGenericTestMergingUpdate.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201209194500_PGGenericTestMergingUpdate.cs b/src/Tgstation.Server.Host/Database/Migrations/20201209194500_PGGenericTestMergingUpdate.cs index 33d3005c57..fe0b34f8c9 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201209194500_PGGenericTestMergingUpdate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201209194500_PGGenericTestMergingUpdate.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201209194554_SLGenericTestMergingUpdate.cs b/src/Tgstation.Server.Host/Database/Migrations/20201209194554_SLGenericTestMergingUpdate.cs index 0e88449ff8..ef9ee91ec7 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201209194554_SLGenericTestMergingUpdate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201209194554_SLGenericTestMergingUpdate.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -57,7 +58,7 @@ namespace Tgstation.Server.Host.Database.Migrations DMApiMinorVersion = table.Column(nullable: true), DMApiPatchVersion = table.Column(nullable: true), GitHubDeploymentId = table.Column(nullable: true), - GitHubRepoId = table.Column(nullable: true) + GitHubRepoId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201214181824_MSAddUserGroups.cs b/src/Tgstation.Server.Host/Database/Migrations/20201214181824_MSAddUserGroups.cs index 3f41b592f4..a11ca56d8f 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201214181824_MSAddUserGroups.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201214181824_MSAddUserGroups.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -51,7 +52,7 @@ namespace Tgstation.Server.Host.Database.Migrations { Id = table.Column(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(maxLength: 100, nullable: false) + Name = table.Column(maxLength: 100, nullable: false), }, constraints: table => { @@ -67,7 +68,7 @@ namespace Tgstation.Server.Host.Database.Migrations AdministrationRights = table.Column(nullable: false), InstanceManagerRights = table.Column(nullable: false), UserId = table.Column(nullable: true), - GroupId = table.Column(nullable: true) + GroupId = table.Column(nullable: true), }, constraints: table => { @@ -100,7 +101,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -241,7 +242,7 @@ namespace Tgstation.Server.Host.Database.Migrations InstanceId = table.Column(type: "bigint", nullable: false), InstanceUserRights = table.Column(type: "decimal(20,0)", nullable: false), RepositoryRights = table.Column(type: "decimal(20,0)", nullable: false), - UserId = table.Column(type: "bigint", nullable: false) + UserId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201214181914_MYAddUserGroups.cs b/src/Tgstation.Server.Host/Database/Migrations/20201214181914_MYAddUserGroups.cs index d6957a88e5..ff0e88f012 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201214181914_MYAddUserGroups.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201214181914_MYAddUserGroups.cs @@ -1,6 +1,7 @@ +using System; + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -52,7 +53,7 @@ namespace Tgstation.Server.Host.Database.Migrations { Id = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(maxLength: 100, nullable: false) + Name = table.Column(maxLength: 100, nullable: false), }, constraints: table => { @@ -68,7 +69,7 @@ namespace Tgstation.Server.Host.Database.Migrations AdministrationRights = table.Column(nullable: false), InstanceManagerRights = table.Column(nullable: false), UserId = table.Column(nullable: true), - GroupId = table.Column(nullable: true) + GroupId = table.Column(nullable: true), }, constraints: table => { @@ -101,7 +102,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -240,7 +241,7 @@ namespace Tgstation.Server.Host.Database.Migrations InstanceId = table.Column(type: "bigint", nullable: false), InstanceUserRights = table.Column(type: "bigint unsigned", nullable: false), RepositoryRights = table.Column(type: "bigint unsigned", nullable: false), - UserId = table.Column(type: "bigint", nullable: false) + UserId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201214182008_PGAddUserGroups.cs b/src/Tgstation.Server.Host/Database/Migrations/20201214182008_PGAddUserGroups.cs index 72586ccabd..c918ba2634 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201214182008_PGAddUserGroups.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201214182008_PGAddUserGroups.cs @@ -1,6 +1,7 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -52,7 +53,7 @@ namespace Tgstation.Server.Host.Database.Migrations { Id = table.Column(nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(maxLength: 100, nullable: false) + Name = table.Column(maxLength: 100, nullable: false), }, constraints: table => { @@ -68,7 +69,7 @@ namespace Tgstation.Server.Host.Database.Migrations AdministrationRights = table.Column(nullable: false), InstanceManagerRights = table.Column(nullable: false), UserId = table.Column(nullable: true), - GroupId = table.Column(nullable: true) + GroupId = table.Column(nullable: true), }, constraints: table => { @@ -101,7 +102,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -240,7 +241,7 @@ namespace Tgstation.Server.Host.Database.Migrations InstanceId = table.Column(type: "bigint", nullable: false), InstanceUserRights = table.Column(type: "numeric(20,0)", nullable: false), RepositoryRights = table.Column(type: "numeric(20,0)", nullable: false), - UserId = table.Column(type: "bigint", nullable: false) + UserId = table.Column(type: "bigint", nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201214182101_SLAddUserGroups.cs b/src/Tgstation.Server.Host/Database/Migrations/20201214182101_SLAddUserGroups.cs index ef40a990e2..d5b760f1be 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201214182101_SLAddUserGroups.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201214182101_SLAddUserGroups.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -25,7 +26,7 @@ namespace Tgstation.Server.Host.Database.Migrations { Id = table.Column(nullable: false) .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 100, nullable: false) + Name = table.Column(maxLength: 100, nullable: false), }, constraints: table => { @@ -41,7 +42,7 @@ namespace Tgstation.Server.Host.Database.Migrations AdministrationRights = table.Column(nullable: false), InstanceManagerRights = table.Column(nullable: false), UserId = table.Column(nullable: true), - GroupId = table.Column(nullable: true) + GroupId = table.Column(nullable: true), }, constraints: table => { @@ -74,7 +75,7 @@ namespace Tgstation.Server.Host.Database.Migrations RepositoryRights = table.Column(nullable: false), ChatBotRights = table.Column(nullable: false), ConfigurationRights = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { @@ -111,7 +112,7 @@ namespace Tgstation.Server.Host.Database.Migrations CreatedById = table.Column(nullable: true), GroupId = table.Column(nullable: true), CanonicalName = table.Column(nullable: false), - LastPasswordUpdate = table.Column(nullable: true) + LastPasswordUpdate = table.Column(nullable: true), }, constraints: table => { @@ -209,7 +210,7 @@ namespace Tgstation.Server.Host.Database.Migrations InstanceId = table.Column(type: "INTEGER", nullable: false), InstanceUserRights = table.Column(type: "INTEGER", nullable: false), RepositoryRights = table.Column(type: "INTEGER", nullable: false), - UserId = table.Column(type: "INTEGER", nullable: false) + UserId = table.Column(type: "INTEGER", nullable: false), }, constraints: table => { @@ -259,7 +260,7 @@ namespace Tgstation.Server.Host.Database.Migrations CreatedById = table.Column(nullable: true), CanonicalName = table.Column(maxLength: 100, nullable: false), LastPasswordUpdate = table.Column(nullable: true), - GroupId = table.Column(nullable: true) + GroupId = table.Column(nullable: true), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201222175310_MSAddSwarmIdentifer.cs b/src/Tgstation.Server.Host/Database/Migrations/20201222175310_MSAddSwarmIdentifer.cs index 70e154ffd9..085a0c9c7c 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201222175310_MSAddSwarmIdentifer.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201222175310_MSAddSwarmIdentifer.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201222175357_MYAddSwarmIdentifer.cs b/src/Tgstation.Server.Host/Database/Migrations/20201222175357_MYAddSwarmIdentifer.cs index 643ffd516c..702d6b7125 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201222175357_MYAddSwarmIdentifer.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201222175357_MYAddSwarmIdentifer.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201222175444_PGAddSwarmIdentifer.cs b/src/Tgstation.Server.Host/Database/Migrations/20201222175444_PGAddSwarmIdentifer.cs index f052cf0609..9a392fa2e0 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201222175444_PGAddSwarmIdentifer.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201222175444_PGAddSwarmIdentifer.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20201222175532_SLAddSwarmIdentifer.cs b/src/Tgstation.Server.Host/Database/Migrations/20201222175532_SLAddSwarmIdentifer.cs index 5d6c11f937..de30adbb85 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20201222175532_SLAddSwarmIdentifer.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20201222175532_SLAddSwarmIdentifer.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -51,7 +52,7 @@ namespace Tgstation.Server.Host.Database.Migrations Online = table.Column(nullable: false), ConfigurationType = table.Column(nullable: false), AutoUpdateInterval = table.Column(nullable: false), - ChatBotLimit = table.Column(nullable: false) + ChatBotLimit = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs index 73f94d358b..17bc2f30c3 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs index 3c7576ba70..305e977673 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs index 0db9ccf53d..0100c8cd4f 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs index c351857d88..abacc20520 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Database.Migrations @@ -39,7 +40,7 @@ namespace Tgstation.Server.Host.Database.Migrations .Annotation("Sqlite:Autoincrement", true), CommitSha = table.Column(maxLength: 40, nullable: false), OriginCommitSha = table.Column(maxLength: 40, nullable: false), - InstanceId = table.Column(nullable: false) + InstanceId = table.Column(nullable: false), }, constraints: table => { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210211173222_MSTruncateInstanceNames.cs b/src/Tgstation.Server.Host/Database/Migrations/20210211173222_MSTruncateInstanceNames.cs index 4b5e0c013a..aa413eb97e 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210211173222_MSTruncateInstanceNames.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210211173222_MSTruncateInstanceNames.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210211173316_MYTruncateInstanceNames.cs b/src/Tgstation.Server.Host/Database/Migrations/20210211173316_MYTruncateInstanceNames.cs index 965260d1cd..b3dbc46de5 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210211173316_MYTruncateInstanceNames.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210211173316_MYTruncateInstanceNames.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210211173407_PGTruncateInstanceNames.cs b/src/Tgstation.Server.Host/Database/Migrations/20210211173407_PGTruncateInstanceNames.cs index 73f768bec0..74970c6db1 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20210211173407_PGTruncateInstanceNames.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20210211173407_PGTruncateInstanceNames.cs @@ -1,5 +1,6 @@ +using System; + using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Tgstation.Server.Host.Database.Migrations { @@ -15,13 +16,13 @@ namespace Tgstation.Server.Host.Database.Migrations throw new ArgumentNullException(nameof(migrationBuilder)); migrationBuilder.AlterColumn( - name: "Name", - table: "Instances", - maxLength: 100, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(10000)", - oldMaxLength: 10000); + name: "Name", + table: "Instances", + maxLength: 100, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(10000)", + oldMaxLength: 10000); } /// @@ -31,13 +32,13 @@ namespace Tgstation.Server.Host.Database.Migrations throw new ArgumentNullException(nameof(migrationBuilder)); migrationBuilder.AlterColumn( - name: "Name", - table: "Instances", - type: "character varying(10000)", - maxLength: 10000, - nullable: false, - oldClrType: typeof(string), - oldMaxLength: 100); - } - } + name: "Name", + table: "Instances", + type: "character varying(10000)", + maxLength: 10000, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 100); + } + } } diff --git a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index 9c4f6dab09..72ed93188d 100644 --- a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -1,12 +1,14 @@ +using System; + using Microsoft.EntityFrameworkCore; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; -using System; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database { /// - /// for MySQL + /// for MySQL. /// sealed class MySqlDatabaseContext : DatabaseContext { @@ -14,11 +16,12 @@ namespace Tgstation.Server.Host.Database protected override DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.Cascade; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the + /// The for the . public MySqlDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) - { } + { + } /// /// Configure the . diff --git a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs index 090ec770ca..c5f68e0cc3 100644 --- a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using System; +using System; + +using Microsoft.EntityFrameworkCore; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database @@ -13,13 +15,14 @@ namespace Tgstation.Server.Host.Database protected override DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.Cascade; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the + /// The for the . public PostgresSqlDatabaseContext( DbContextOptions dbContextOptions) : base(dbContextOptions) - { } + { + } /// /// Configure the . diff --git a/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs index 3b5eb77b83..c0b06ad1f5 100644 --- a/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs @@ -1,20 +1,23 @@ -using Microsoft.EntityFrameworkCore; -using System; +using System; + +using Microsoft.EntityFrameworkCore; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database { /// - /// for Sqlserver + /// for Sqlserver. /// sealed class SqlServerDatabaseContext : DatabaseContext { /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the + /// The for the . public SqlServerDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) - { } + { + } /// /// Configure the . diff --git a/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs index a22872e935..eada1f31a9 100644 --- a/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs @@ -1,7 +1,9 @@ +using System; +using System.Linq; + using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System; -using System.Linq; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database @@ -17,11 +19,12 @@ namespace Tgstation.Server.Host.Database public static bool DesignTime { get; set; } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The for the + /// The for the . public SqliteDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) - { } + { + } /// /// Configure the . diff --git a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs index 328fb766ce..ad7f54b552 100644 --- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -1,12 +1,14 @@ +using System; +using System.Globalization; +using System.Net; + using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; -using System.Globalization; -using System.Net; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.System; @@ -14,25 +16,19 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Extensions { /// - /// Extensions for + /// Extensions for . /// static class ApplicationBuilderExtensions { /// - /// Gets a from a given + /// Return a for s. /// - /// The to get the from - /// A new - static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService().CreateLogger(typeof(ApplicationBuilderExtensions)); - - /// - /// Return a for s - /// - /// The to configure + /// The to configure. public static void UseDbConflictHandling(this IApplicationBuilder applicationBuilder) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); + applicationBuilder.Use(async (context, next) => { var logger = GetLogger(context); @@ -51,19 +47,19 @@ namespace Tgstation.Server.Host.Extensions logger.LogDebug(e, "Database conflict!"); await new ConflictObjectResult(new ErrorMessageResponse(ErrorCode.DatabaseIntegrityConflict) { - AdditionalData = String.Format(CultureInfo.InvariantCulture, (e.InnerException ?? e).Message) + AdditionalData = String.Format(CultureInfo.InvariantCulture, (e.InnerException ?? e).Message), }).ExecuteResultAsync(new ActionContext { - HttpContext = context + HttpContext = context, }).ConfigureAwait(false); } }); } /// - /// Suppress TaskCanceledException warnings when a user aborts a request + /// Suppress warnings when a user aborts a request. /// - /// The to configure + /// The to configure. public static void UseCancelledRequestSuppression(this IApplicationBuilder applicationBuilder) { if (applicationBuilder == null) @@ -83,9 +79,9 @@ namespace Tgstation.Server.Host.Extensions } /// - /// Suppress all in flight exceptions with error 500. + /// Suppress all in flight s for the request with error 500. /// - /// The to configure + /// The to configure. public static void UseServerErrorHandling(this IApplicationBuilder applicationBuilder) { if (applicationBuilder == null) @@ -103,14 +99,14 @@ namespace Tgstation.Server.Host.Extensions await new JsonResult( new ErrorMessageResponse(ErrorCode.InternalServerError) { - AdditionalData = e.ToString() + AdditionalData = e.ToString(), }) { - StatusCode = (int)HttpStatusCode.InternalServerError + StatusCode = (int)HttpStatusCode.InternalServerError, } .ExecuteResultAsync(new ActionContext { - HttpContext = context + HttpContext = context, }) .ConfigureAwait(false); } @@ -135,5 +131,12 @@ namespace Tgstation.Server.Host.Extensions await next().ConfigureAwait(false); }); } + + /// + /// Gets a from a given . + /// + /// The to get the from. + /// A new . + static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService().CreateLogger(typeof(ApplicationBuilderExtensions)); } } diff --git a/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs index be2afe14bd..e2ef1bd72b 100644 --- a/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Extensions { /// - /// Extensions for the . + /// Extensions for the class. /// static class ChatChannelExtensions { diff --git a/src/Tgstation.Server.Host/Extensions/Converters/BoolConverter.cs b/src/Tgstation.Server.Host/Extensions/Converters/BoolConverter.cs index 3ce617cdb9..ece3052282 100644 --- a/src/Tgstation.Server.Host/Extensions/Converters/BoolConverter.cs +++ b/src/Tgstation.Server.Host/Extensions/Converters/BoolConverter.cs @@ -1,5 +1,6 @@ +using System; + using Newtonsoft.Json; -using System; namespace Tgstation.Server.Host.Extensions.Converters { diff --git a/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs b/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs index 4d91a10ebd..119414f2af 100644 --- a/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs +++ b/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs @@ -1,6 +1,9 @@ +using System; + using Newtonsoft.Json; -using System; + using Tgstation.Server.Api; + using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; diff --git a/src/Tgstation.Server.Host/Extensions/DatabaseCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/DatabaseCollectionExtensions.cs index 6599830847..8514452de5 100644 --- a/src/Tgstation.Server.Host/Extensions/DatabaseCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/DatabaseCollectionExtensions.cs @@ -1,7 +1,9 @@ -using Microsoft.EntityFrameworkCore; -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; + using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; @@ -24,7 +26,7 @@ namespace Tgstation.Server.Host.Extensions .Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName)) .Select(x => new User { - Id = x.Id + Id = x.Id, }) .FirstAsync(cancellationToken); } diff --git a/src/Tgstation.Server.Host/Extensions/DateTimeOffsetExtensions.cs b/src/Tgstation.Server.Host/Extensions/DateTimeOffsetExtensions.cs index f3f36d3b23..44b0669314 100644 --- a/src/Tgstation.Server.Host/Extensions/DateTimeOffsetExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/DateTimeOffsetExtensions.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Tgstation.Server.Host.Extensions { /// - /// Extension methods for the . + /// Extension methods for the class. /// static class DateTimeOffsetExtensions { diff --git a/src/Tgstation.Server.Host/Extensions/HostBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/HostBuilderExtensions.cs index 5f29cca330..1ff0081da6 100644 --- a/src/Tgstation.Server.Host/Extensions/HostBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/HostBuilderExtensions.cs @@ -1,16 +1,18 @@ -using Microsoft.Extensions.Hosting; -using System; +using System; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Host.Setup; namespace Tgstation.Server.Host.Extensions { /// - /// Extension methods for the . + /// Extension methods for the class. /// static class HostBuilderExtensions { /// - /// Workaround for using the for host startup. + /// Workaround for using the class for host startup. /// /// The to configure. /// The configured . diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index e98ff88694..d383cdccba 100644 --- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -1,17 +1,19 @@ +using System; +using System.Diagnostics; +using System.Globalization; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Configuration; -using System; -using System.Diagnostics; -using System.Globalization; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Extensions { /// - /// Extensions for + /// Extensions for . /// static class ServiceCollectionExtensions { @@ -21,12 +23,12 @@ namespace Tgstation.Server.Host.Extensions public const string SerilogContextTemplate = "(Instance:{Instance}|Job:{Job}|Request:{Request}|User:{User}|Monitor:{Monitor}|Bridge:{Bridge}|Chat:{ChatMessage}"; /// - /// Add a standard binding + /// Add a standard binding. /// - /// The to bind. Must have a const/static field named "Section" - /// The to configure - /// The containing the - /// + /// The to bind. Must have a const/static field named "Section". + /// The to configure. + /// The containing the . + /// . public static IServiceCollection UseStandardConfig(this IServiceCollection serviceCollection, IConfiguration configuration) where TConfig : class { if (serviceCollection == null) diff --git a/src/Tgstation.Server.Host/Extensions/SocketExtensions.cs b/src/Tgstation.Server.Host/Extensions/SocketExtensions.cs index 83eec75d21..a9e6a97532 100644 --- a/src/Tgstation.Server.Host/Extensions/SocketExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/SocketExtensions.cs @@ -4,7 +4,7 @@ using System.Net.Sockets; namespace Tgstation.Server.Host.Extensions { /// - /// Extension methods for the . + /// Extension methods for the class. /// static class SocketExtensions { diff --git a/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs b/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs index b71e5edc60..ef9bde1e6f 100644 --- a/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Extensions { /// - /// Extensions for the . + /// Extensions for the class. /// static class TaskExtensions { diff --git a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs index e1fafbddc5..9b6a88b0a2 100644 --- a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs @@ -1,8 +1,10 @@ +using System; + using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; + using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -12,12 +14,12 @@ using Tgstation.Server.Host.Setup; namespace Tgstation.Server.Host.Extensions { /// - /// Extension methods for the . + /// Extension methods for the class. /// static class WebHostBuilderExtensions { /// - /// Workaround for using the for server startup. + /// Workaround for using the class for server startup. /// /// The to configure. /// The to use. diff --git a/src/Tgstation.Server.Host/IO/Console.cs b/src/Tgstation.Server.Host/IO/Console.cs index 66f1908e37..3a008f1704 100644 --- a/src/Tgstation.Server.Host/IO/Console.cs +++ b/src/Tgstation.Server.Host/IO/Console.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,12 +20,12 @@ namespace Tgstation.Server.Host.IO readonly CancellationTokenSource cancelKeyCts; /// - /// If the was disposed; + /// If the was disposed. /// bool disposed; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// public Console() { @@ -50,69 +50,84 @@ namespace Tgstation.Server.Host.IO } } + /// + public Task PressAnyKeyAsync(CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + CheckAvailable(); + global::System.Console.Read(); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + public Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + // TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline + CheckAvailable(); + if (!usePasswordChar) + return global::System.Console.ReadLine(); + + var passwordBuilder = new StringBuilder(); + do + { + var keyDescription = global::System.Console.ReadKey(true); + if (keyDescription.Key == ConsoleKey.Enter) + break; + else if (keyDescription.Key == ConsoleKey.Backspace) + { + if (passwordBuilder.Length > 0) + { + --passwordBuilder.Length; + global::System.Console.Write("\b \b"); + } + } + else if (keyDescription.KeyChar != '\u0000') + { + // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc + passwordBuilder.Append(keyDescription.KeyChar); + global::System.Console.Write('*'); + } + } + while (!cancellationToken.IsCancellationRequested); + + cancellationToken.ThrowIfCancellationRequested(); + global::System.Console.WriteLine(); + return passwordBuilder.ToString(); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + public Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + CheckAvailable(); + if (text == null) + { + if (!newLine) + throw new InvalidOperationException("Cannot write null text without a new line!"); + global::System.Console.WriteLine(); + } + else if (newLine) + global::System.Console.WriteLine(text); + else + global::System.Console.Write(text); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + /// Assert that the is available, throwing an otherwise. + /// void CheckAvailable() { if (!Available) throw new InvalidOperationException("Console unavailable"); } - - /// - public Task PressAnyKeyAsync(CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - CheckAvailable(); - global::System.Console.Read(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); - - /// - public Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - // TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline - CheckAvailable(); - if (!usePasswordChar) - return global::System.Console.ReadLine(); - - var passwordBuilder = new StringBuilder(); - do - { - var keyDescription = global::System.Console.ReadKey(true); - if (keyDescription.Key == ConsoleKey.Enter) - break; - else if (keyDescription.Key == ConsoleKey.Backspace) - { - if (passwordBuilder.Length > 0) - { - --passwordBuilder.Length; - global::System.Console.Write("\b \b"); - } - } - else if (keyDescription.KeyChar != '\u0000') - { - // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc - passwordBuilder.Append(keyDescription.KeyChar); - global::System.Console.Write('*'); - } - } - while (!cancellationToken.IsCancellationRequested); - - cancellationToken.ThrowIfCancellationRequested(); - global::System.Console.WriteLine(); - return passwordBuilder.ToString(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); - - /// - public Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - CheckAvailable(); - if (text == null) - { - if (!newLine) - throw new InvalidOperationException("Cannot write null text without a new line!"); - global::System.Console.WriteLine(); - } - else if (newLine) - global::System.Console.WriteLine(text); - else - global::System.Console.Write(text); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index ddf3c69d27..d66cd2ec68 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.IO { /// - /// that resolves paths to + /// that resolves paths to . /// class DefaultIOManager : IIOManager { @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.IO public const string CurrentDirectory = "."; /// - /// Default buffer size used by .NET + /// Default buffer size used by .NET. /// public const int DefaultBufferSize = 4096; @@ -30,11 +30,11 @@ namespace Tgstation.Server.Host.IO public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None; /// - /// Recursively empty a directory + /// Recursively empty a directory. /// - /// of the directory to empty - /// The for the operation - /// A representing the running operation + /// of the directory to empty. + /// The for the operation. + /// A representing the running operation. static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken) { var tasks = new List(); @@ -64,54 +64,6 @@ namespace Tgstation.Server.Host.IO dir.Delete(true); } - /// - /// Copies a directory from to - /// - /// The source directory path - /// The destination directory path - /// Files and folders to ignore at the root level - /// The for the operation - /// A of s representing the running operation - IEnumerable CopyDirectoryImpl(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) - { - var dir = new DirectoryInfo(src); - var atLeastOneSubDir = false; - foreach (var I in dir.EnumerateDirectories()) - { - if (ignore != null && ignore.Contains(I.Name)) - continue; - foreach (var J in CopyDirectoryImpl(I.FullName, Path.Combine(dest, I.Name), null, cancellationToken)) - { - atLeastOneSubDir = true; - yield return J; - } - } - - async Task CopyThisDirectory() - { - if (!atLeastOneSubDir) - await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls - - var tasks = new List(); - - await dir.EnumerateFiles() - .ToAsyncEnumerable() - .ForEachAsync( - fileInfo => - { - if (ignore != null && ignore.Contains(fileInfo.Name)) - return; - tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken)); - }, - cancellationToken) - .ConfigureAwait(false); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - yield return CopyThisDirectory(); - } - /// public async Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) { @@ -192,43 +144,55 @@ namespace Tgstation.Server.Host.IO public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path))); /// - public Task> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - path = ResolvePath(path); - if (extension == null) - throw new ArgumentNullException(extension); - var results = new List(); - foreach (var I in Directory.EnumerateFiles( - path, - $"*.{extension}", - recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + public Task> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - cancellationToken.ThrowIfCancellationRequested(); - results.Add(I); - } + path = ResolvePath(path); + if (extension == null) + throw new ArgumentNullException(extension); + var results = new List(); + foreach (var fileName in Directory.EnumerateFiles( + path, + $"*.{extension}", + recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + cancellationToken.ThrowIfCancellationRequested(); + results.Add(fileName); + } - return results; - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + return results; + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); /// - public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); - destination = ResolvePath(destination); - File.Move(source, destination); - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); + destination = ResolvePath(destination); + File.Move(source, destination); + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); /// - public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); - destination = ResolvePath(destination); - Directory.Move(source, destination); - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); + destination = ResolvePath(destination); + Directory.Move(source, destination); + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); /// public async Task ReadAllBytes(string path, CancellationToken cancellationToken) @@ -268,34 +232,42 @@ namespace Tgstation.Server.Host.IO } /// - public Task> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - path = ResolvePath(path); - var results = new List(); - cancellationToken.ThrowIfCancellationRequested(); - foreach (var I in Directory.EnumerateDirectories(path)) + public Task> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - results.Add(I); + path = ResolvePath(path); + var results = new List(); cancellationToken.ThrowIfCancellationRequested(); - } + foreach (var directoryName in Directory.EnumerateDirectories(path)) + { + results.Add(directoryName); + cancellationToken.ThrowIfCancellationRequested(); + } - return (IReadOnlyList)results; - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + return (IReadOnlyList)results; + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); /// - public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - path = ResolvePath(path); - var results = new List(); - cancellationToken.ThrowIfCancellationRequested(); - foreach (var I in Directory.EnumerateFiles(path)) + public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - results.Add(I); + path = ResolvePath(path); + var results = new List(); cancellationToken.ThrowIfCancellationRequested(); - } + foreach (var fileName in Directory.EnumerateFiles(path)) + { + results.Add(fileName); + cancellationToken.ThrowIfCancellationRequested(); + } - return (IReadOnlyList)results; - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + return (IReadOnlyList)results; + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) @@ -322,28 +294,98 @@ namespace Tgstation.Server.Host.IO } /// - public Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + path = ResolvePath(path); + if (zipFile == null) + throw new ArgumentNullException(nameof(zipFile)); + + using var archive = new ZipArchive(zipFile, ZipArchiveMode.Read); + archive.ExtractToDirectory(path); + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + public bool PathContainsParentAccess(string path) => path + ?.Split( + new[] + { + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar, + }) + .Any(x => x == "..") + ?? throw new ArgumentNullException(nameof(path)); + + /// + public Task GetLastModified(string path, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + path = ResolvePath(path ?? throw new ArgumentNullException(nameof(path))); + var fileInfo = new FileInfo(path); + return new DateTimeOffset(fileInfo.LastWriteTimeUtc); + }, + cancellationToken, + BlockingTaskCreationOptions, + TaskScheduler.Current); + + /// + public FileStream GetFileStream(string path, bool shareWrite) => new FileStream( + ResolvePath(path), + FileMode.Open, + FileAccess.Read, + FileShare.Read | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None), + DefaultBufferSize, + true); + + /// + /// Copies a directory from to . + /// + /// The source directory path. + /// The destination directory path. + /// Files and folders to ignore at the root level. + /// The for the operation. + /// A of s representing the running operation. + IEnumerable CopyDirectoryImpl(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) { - path = ResolvePath(path); - if (zipFile == null) - throw new ArgumentNullException(nameof(zipFile)); + var dir = new DirectoryInfo(src); + var atLeastOneSubDir = false; + foreach (var subDirectory in dir.EnumerateDirectories()) + { + if (ignore != null && ignore.Contains(subDirectory.Name)) + continue; + foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, cancellationToken)) + { + atLeastOneSubDir = true; + yield return copyTask; + } + } - using var archive = new ZipArchive(zipFile, ZipArchiveMode.Read); - archive.ExtractToDirectory(path); - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + async Task CopyThisDirectory() + { + if (!atLeastOneSubDir) + await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls - /// - public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path)); + var tasks = new List(); - /// - public Task GetLastModified(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - path = ResolvePath(path ?? throw new ArgumentNullException(nameof(path))); - var fileInfo = new FileInfo(path); - return new DateTimeOffset(fileInfo.LastWriteTimeUtc); - }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); + await dir.EnumerateFiles() + .ToAsyncEnumerable() + .ForEachAsync( + fileInfo => + { + if (ignore != null && ignore.Contains(fileInfo.Name)) + return; + tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken)); + }, + cancellationToken) + .ConfigureAwait(false); - /// - public FileStream GetFileStream(string path, bool shareWrite) => new FileStream(ResolvePath(path), FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None), DefaultBufferSize, true); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + yield return CopyThisDirectory(); + } } } diff --git a/src/Tgstation.Server.Host/IO/IConsole.cs b/src/Tgstation.Server.Host/IO/IConsole.cs index 6ec3962bf6..8fbe368f39 100644 --- a/src/Tgstation.Server.Host/IO/IConsole.cs +++ b/src/Tgstation.Server.Host/IO/IConsole.cs @@ -4,12 +4,12 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.IO { /// - /// Abstraction for + /// Abstraction for . /// interface IConsole { /// - /// If the is visible to the user + /// If the is visible to the user. /// bool Available { get; } @@ -19,27 +19,27 @@ namespace Tgstation.Server.Host.IO CancellationToken CancelKeyPress { get; } /// - /// Write some to the + /// Write some to the . /// - /// The to write - /// If there should be a new line after the - /// The for the operation - /// A representing the running operation + /// The to write. + /// If there should be a new line after the . + /// The for the operation. + /// A representing the running operation. Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken); /// - /// Wait for a key press on the + /// Wait for a key press on the . /// - /// The for the operations - /// A representing the running operation + /// The for the operations. + /// A representing the running operation. Task PressAnyKeyAsync(CancellationToken cancellationToken); /// - /// Read a line from the + /// Read a line from the . /// - /// If the input should be retrieved using the '*' character - /// The for the operation - /// A resulting in the read by the + /// If the input should be retrieved using the '*' character. + /// The for the operation. + /// A resulting in the read by the . Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 90c13b8cdb..13b360d130 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.IO { /// - /// Interface for using filesystems + /// Interface for using filesystems. /// public interface IIOManager { @@ -18,182 +18,182 @@ namespace Tgstation.Server.Host.IO string ResolvePath(); /// - /// Retrieve the full path of some given a relative path. Must be used before passing relative paths to other APIs. All other operations in this call this internally on given paths + /// Retrieve the full path of some given a relative path. Must be used before passing relative paths to other APIs. All other operations in this call this internally on given paths. /// - /// Some path to retrieve the full path of + /// Some path to retrieve the full path of. /// as a full path. string ResolvePath(string path); /// - /// Gets the file name portion of a + /// Gets the file name portion of a . /// - /// The path to get the file name of - /// The file name portion of + /// The path to get the file name of. + /// The file name portion of . string GetFileName(string path); /// - /// Gets the file name portion of a with + /// Gets the file name portion of a with. /// - /// The path to get the file name of - /// The file name portion of + /// The path to get the file name of. + /// The file name portion of . string GetFileNameWithoutExtension(string path); /// - /// Check if a contains the '..' parent directory accessor + /// Check if a contains the '..' parent directory accessor. /// - /// The path to check - /// if contains a '..' accessor, otherwise + /// The path to check. + /// if contains a '..' accessor, otherwise. bool PathContainsParentAccess(string path); /// - /// Copies a directory from to + /// Copies a directory from to . /// - /// The source directory path - /// The destination directory path - /// Files and folders to ignore at the root level - /// The for the operation - /// A representing the running operation + /// The source directory path. + /// The destination directory path. + /// Files and folders to ignore at the root level. + /// The for the operation. + /// A representing the running operation. Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken); /// - /// Check that the file at exists + /// Check that the file at exists. /// - /// The file to check for existence - /// The for the operation - /// A resulting in if the file at exists, otherwise + /// The file to check for existence. + /// The for the operation. + /// A resulting in if the file at exists, otherwise. Task FileExists(string path, CancellationToken cancellationToken); /// - /// Check that the directory at exists + /// Check that the directory at exists. /// - /// The directory to check for existence - /// The for the operation - /// A resulting in if the directory at exists, otherwise + /// The directory to check for existence. + /// The for the operation. + /// A resulting in if the directory at exists, otherwise. Task DirectoryExists(string path, CancellationToken cancellationToken); /// - /// Returns all the contents of a file at as a array + /// Returns all the contents of a file at as a array. /// - /// The path of the file to read - /// A for the operation - /// A that results in the contents of a file at + /// The path of the file to read. + /// A for the operation. + /// A that results in the contents of a file at . Task ReadAllBytes(string path, CancellationToken cancellationToken); /// - /// Returns directory names in a given + /// Returns directory names in a given . /// - /// The path to search for directories - /// The for the operation - /// A resulting in the directories in + /// The path to search for directories. + /// The for the operation. + /// A resulting in the directories in . Task> GetDirectories(string path, CancellationToken cancellationToken); /// - /// Returns file names in a given + /// Returns file names in a given . /// - /// The path to search for files - /// The for the operation - /// A resulting in the files in + /// The path to search for files. + /// The for the operation. + /// A resulting in the files in . Task> GetFiles(string path, CancellationToken cancellationToken); /// - /// Writes some to a file at overwriting previous content + /// Writes some to a file at overwriting previous content. /// - /// The path of the file to write - /// The contents of the file - /// A for the operation - /// A representing the running operation + /// The path of the file to write. + /// The contents of the file. + /// A for the operation. + /// A representing the running operation. Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken); /// - /// Copy a file from to + /// Copy a file from to . /// - /// The source file to copy - /// The destination path - /// A for the operation - /// A representing the running operation + /// The source file to copy. + /// The destination path. + /// A for the operation. + /// A representing the running operation. Task CopyFile(string src, string dest, CancellationToken cancellationToken); /// - /// Gets the directory portion of a given + /// Gets the directory portion of a given . /// - /// A path to check - /// The directory portion of the given + /// A path to check. + /// The directory portion of the given . string GetDirectoryName(string path); /// - /// Gets a list of files in with the given + /// Gets a list of files in with the given . /// - /// The directory which contains the files - /// The extension to look for without the preceeding "." + /// The directory which contains the files. + /// The extension to look for without the preceeding ".". /// If the search should be recursive. - /// A for the operation - /// A resulting in a list of paths to files in with the given + /// A for the operation. + /// A resulting in a list of paths to files in with the given . Task> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken); /// - /// Deletes a file at + /// Deletes a file at . /// - /// The path of the file to delete - /// A for the operation - /// A representing the running operation + /// The path of the file to delete. + /// A for the operation. + /// A representing the running operation. Task DeleteFile(string path, CancellationToken cancellationToken); /// - /// Create a directory at + /// Create a directory at . /// - /// The path of the directory to create - /// A for the operation - /// A representing the running operation + /// The path of the directory to create. + /// A for the operation. + /// A representing the running operation. Task CreateDirectory(string path, CancellationToken cancellationToken); /// /// Recursively delete a directory, removes and does not enter any symlinks encounterd. /// - /// The path to the directory to delete - /// A for the operation - /// A representing the running operation + /// The path to the directory to delete. + /// A for the operation. + /// A representing the running operation. Task DeleteDirectory(string path, CancellationToken cancellationToken); /// - /// Combines an array of strings into a path + /// Combines an array of strings into a path. /// - /// The paths to combine - /// The combined path + /// The paths to combine. + /// The combined path. string ConcatPath(params string[] paths); /// - /// Moves a file at to + /// Moves a file at to . /// - /// The source file path - /// The destination path - /// A for the operation - /// A representing the running operation + /// The source file path. + /// The destination path. + /// A for the operation. + /// A representing the running operation. Task MoveFile(string source, string destination, CancellationToken cancellationToken); /// - /// Moves a directory at to + /// Moves a directory at to . /// - /// The source directory path - /// The destination path - /// A for the operation - /// A representing the running operation + /// The source directory path. + /// The destination path. + /// A for the operation. + /// A representing the running operation. Task MoveDirectory(string source, string destination, CancellationToken cancellationToken); /// - /// Downloads a file from + /// Downloads a file from . /// - /// The URL to download - /// A for the operation - /// A resulting in the s of the downloaded file + /// The URL to download. + /// A for the operation. + /// A resulting in the s of the downloaded file. Task DownloadFile(Uri url, CancellationToken cancellationToken); /// - /// Extract a set of to a given + /// Extract a set of to a given . /// - /// The path to unzip to - /// The of the - /// The for the operation - /// A representing the running operation + /// The path to unzip to. + /// The of the . + /// The for the operation. + /// A representing the running operation. Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs index f8c0f4042a..e99a2eb1e4 100644 --- a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs @@ -1,14 +1,14 @@ namespace Tgstation.Server.Host.IO { /// - /// Handles changing file modes/permissions after writing + /// Handles changing file modes/permissions after writing. /// interface IPostWriteHandler { /// - /// For handling system specific necessities after a write + /// For handling system specific necessities after a write. /// - /// The full path to the file that was written + /// The full path to the file that was written. void HandleWrite(string filePath); } } diff --git a/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs b/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs index b92269404b..c762395fca 100644 --- a/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs @@ -4,17 +4,17 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.IO { /// - /// For creating filesystem symbolic links + /// For creating filesystem symbolic links. /// interface ISymlinkFactory { /// - /// Create a symbolic link + /// Create a symbolic link. /// - /// The path to the hard target - /// The path to the link - /// The for the operation - /// A representing the running operation + /// The path to the hard target. + /// The path to the link. + /// The for the operation. + /// A representing the running operation. Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 20e6656aec..36f90140aa 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -1,67 +1,67 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Threading; namespace Tgstation.Server.Host.IO { /// - /// For accessing the disk in a synchronous manner + /// For accessing the disk in a synchronous manner. /// interface ISynchronousIOManager { /// - /// Enumerate files in a given + /// Enumerate files in a given . /// - /// The path to look for files in - /// The for the operation - /// A of file names in + /// The path to look for files in. + /// The for the operation. + /// A of file names in . IEnumerable GetFiles(string path, CancellationToken cancellationToken); /// - /// Enumerate directories in a given + /// Enumerate directories in a given . /// - /// The path to look for directories in - /// The for the operation - /// A of directory names in + /// The path to look for directories in. + /// The for the operation. + /// A of directory names in . IEnumerable GetDirectories(string path, CancellationToken cancellationToken); /// - /// Create an empty directory at + /// Create an empty directory at . /// - /// The path to create - /// The for the operation. Usage may result in partial writes - /// if the directory already existed, otherwise + /// The path to create. + /// The for the operation. Usage may result in partial writes. + /// if the directory already existed, otherwise. bool CreateDirectory(string path, CancellationToken cancellationToken); /// - /// Read the s of a file at a given + /// Read the s of a file at a given . /// - /// The path of the file to read - /// A array representing the contents of the file at + /// The path of the file to read. + /// A array representing the contents of the file at . byte[] ReadFile(string path); /// - /// Deletes a directory at if it's empty + /// Deletes a directory at if it's empty. /// - /// The path of the directory to delete - /// if the directory does not exist or is empty and was deleted. otherwise + /// The path of the directory to delete. + /// if the directory does not exist or is empty and was deleted. otherwise. bool DeleteDirectory(string path); /// - /// Write to a file at a given + /// Write to a file at a given . /// - /// The path to the file to write - /// A containing the new contents of the file - /// The function only succeeds if this parameter matches the SHA-1 hash of the contents of the current file. Contains the SHA1 of the file on disk once the function returns - /// The for the operation - /// on success, if the operation failed due to not matching the file's contents + /// The path to the file to write. + /// A containing the new contents of the file. + /// The function only succeeds if this parameter matches the SHA-1 hash of the contents of the current file. Contains the SHA1 of the file on disk once the function returns. + /// The for the operation. + /// on success, if the operation failed due to not matching the file's contents. bool WriteFileChecked(string path, Stream data, ref string sha1InOut, CancellationToken cancellationToken); /// - /// Checks if a given is a directory + /// Checks if a given is a directory. /// - /// The path to check - /// if is a directory, otherwise + /// The path to check. + /// if is a directory, otherwise. bool IsDirectory(string path); } } diff --git a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs index d7ddb4ab79..b2f9bf9143 100644 --- a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs @@ -1,22 +1,23 @@ -using Microsoft.Extensions.Logging; +using System; + +using Microsoft.Extensions.Logging; using Mono.Unix; using Mono.Unix.Native; -using System; namespace Tgstation.Server.Host.IO { /// - /// for POSIX systems + /// for POSIX systems. /// sealed class PosixPostWriteHandler : IPostWriteHandler { /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public PosixPostWriteHandler(ILogger logger) diff --git a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs index 997fa3d005..4644080a10 100644 --- a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs @@ -1,33 +1,38 @@ -using Mono.Unix; -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using Mono.Unix; + namespace Tgstation.Server.Host.IO { /// - /// for posix systems + /// for posix systems. /// sealed class PosixSymlinkFactory : ISymlinkFactory { /// - public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (targetPath == null) - throw new ArgumentNullException(nameof(targetPath)); - if (linkPath == null) - throw new ArgumentNullException(nameof(linkPath)); + public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (targetPath == null) + throw new ArgumentNullException(nameof(targetPath)); + if (linkPath == null) + throw new ArgumentNullException(nameof(linkPath)); - UnixFileSystemInfo fsInfo; - var isFile = File.Exists(targetPath); - cancellationToken.ThrowIfCancellationRequested(); - if (isFile) - fsInfo = new UnixFileInfo(targetPath); - else - fsInfo = new UnixDirectoryInfo(targetPath); - cancellationToken.ThrowIfCancellationRequested(); - fsInfo.CreateSymbolicLink(linkPath); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + UnixFileSystemInfo fsInfo; + var isFile = File.Exists(targetPath); + cancellationToken.ThrowIfCancellationRequested(); + if (isFile) + fsInfo = new UnixFileInfo(targetPath); + else + fsInfo = new UnixDirectoryInfo(targetPath); + cancellationToken.ThrowIfCancellationRequested(); + fsInfo.CreateSymbolicLink(linkPath); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs index 32ef957809..0bb4a879ed 100644 --- a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs @@ -4,20 +4,20 @@ using System.IO; namespace Tgstation.Server.Host.IO { /// - /// An that resolve relative paths from another to a subdirectory of that + /// An that resolve relative paths from another to a subdirectory of that. /// sealed class ResolvingIOManager : DefaultIOManager { /// - /// Path to the subdirectory attached to path resolutions + /// Path to the subdirectory attached to path resolutions. /// readonly string subdirectory; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The that resolves to the directory to work out of - /// The value of + /// The that resolves to the directory to work out of. + /// The value of . public ResolvingIOManager(IIOManager parent, string subdirectory) { if (parent == null) @@ -36,4 +36,4 @@ namespace Tgstation.Server.Host.IO return path; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index c3c66bf92e..cf076297cd 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -40,9 +40,9 @@ namespace Tgstation.Server.Host.IO /// public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) { - foreach (var I in Directory.EnumerateDirectories(path)) + foreach (var directoryName in Directory.EnumerateDirectories(path)) { - yield return Path.GetFileName(I); + yield return Path.GetFileName(directoryName); cancellationToken.ThrowIfCancellationRequested(); } } @@ -50,9 +50,9 @@ namespace Tgstation.Server.Host.IO /// public IEnumerable GetFiles(string path, CancellationToken cancellationToken) { - foreach (var I in Directory.EnumerateFiles(path)) + foreach (var fileName in Directory.EnumerateFiles(path)) { - yield return Path.GetFileName(I); + yield return Path.GetFileName(fileName); cancellationToken.ThrowIfCancellationRequested(); } } diff --git a/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs index 86907933fe..bec6986208 100644 --- a/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.IO { /// - /// for Windows systems + /// for Windows systems. /// sealed class WindowsPostWriteHandler : IPostWriteHandler { diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index 99f4221b6f..ae3a261c3d 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -1,42 +1,47 @@ -using BetterWin32Errors; -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using BetterWin32Errors; + namespace Tgstation.Server.Host.IO { /// - /// for windows systems + /// for windows systems. /// sealed class WindowsSymlinkFactory : ISymlinkFactory { /// - public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (targetPath == null) - throw new ArgumentNullException(nameof(targetPath)); - if (linkPath == null) - throw new ArgumentNullException(nameof(linkPath)); + public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (targetPath == null) + throw new ArgumentNullException(nameof(targetPath)); + if (linkPath == null) + throw new ArgumentNullException(nameof(linkPath)); - // check if its not a file - var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory; + // check if its not a file + var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory; - /* - * no don't fucking use this - * sure it works in SOME cases - * i.e. win10 1803+ and IN DEVELOPER MODE - * other times it throws ERROR_INVALID_PARAMETER - * but the fucking worst is there are some configurations of windows that accept the argument and allow the function to succeed - * BUT IT DOESN'T CREATE THE FUCKING LINK - * I AM NOT DEBUGGING THAT SHIT AGAIN AHHH + /* + * no don't fucking use this + * sure it works in SOME cases + * i.e. win10 1803+ and IN DEVELOPER MODE + * other times it throws ERROR_INVALID_PARAMETER + * but the fucking worst is there are some configurations of windows that accept the argument and allow the function to succeed + * BUT IT DOESN'T CREATE THE FUCKING LINK + * I AM NOT DEBUGGING THAT SHIT AGAIN AHHH - flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; - */ + flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + */ - cancellationToken.ThrowIfCancellationRequested(); - if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) - throw new Win32Exception(); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + cancellationToken.ThrowIfCancellationRequested(); + if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) + throw new Win32Exception(); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs index 12fec2e42d..7ee759ec86 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -4,20 +4,20 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host { /// - /// Represents the host + /// Represents the host. /// public interface IServer { /// - /// If the should restart + /// If the should restart. /// bool RestartRequested { get; } /// - /// Runs the + /// Runs the . /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. Task Run(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/IServerFactory.cs b/src/Tgstation.Server.Host/IServerFactory.cs index 1fd1eddee5..679ab1d421 100644 --- a/src/Tgstation.Server.Host/IServerFactory.cs +++ b/src/Tgstation.Server.Host/IServerFactory.cs @@ -1,11 +1,12 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host { /// - /// For creating s + /// For creating s. /// interface IServerFactory { @@ -15,10 +16,10 @@ namespace Tgstation.Server.Host IIOManager IOManager { get; } /// - /// Create a + /// Create a . /// - /// The arguments for the - /// The directory in which to install server updates + /// The arguments for the . + /// The directory in which to install server updates. /// The for the operation. /// A resulting in a new if it should be run, otherwise. Task CreateServer(string[] args, string updatePath, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Jobs/IJobManager.cs b/src/Tgstation.Server.Host/Jobs/IJobManager.cs index 04e8079022..f7cef9993d 100644 --- a/src/Tgstation.Server.Host/Jobs/IJobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/IJobManager.cs @@ -1,48 +1,50 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; +using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Jobs { /// - /// Manages the runtime of s + /// Manages the runtime of s. /// public interface IJobManager : IHostedService { /// - /// Get the for a + /// Get the for a . /// - /// The to get for - /// The of + /// The to get for. + /// The of . int? JobProgress(Api.Models.Internal.Job job); /// - /// Registers a given and begins running it + /// Registers a given and begins running it. /// /// The . Should at least have and . If is , the TGS user will be used. /// The for the . - /// The for the operation - /// A representing a running operation + /// The for the operation. + /// A representing a running operation. Task RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken); /// - /// Wait for a given to complete + /// Wait for a given to complete. /// - /// The to wait for + /// The to wait for. /// The to cancel the . If the TGS user will be used. - /// A that will cancel the - /// The for the operation - /// A representing the + /// A that will cancel the . + /// The for the operation. + /// A representing the . Task WaitForJobCompletion(Job job, User canceller, CancellationToken jobCancellationToken, CancellationToken cancellationToken); /// - /// Cancels a give + /// Cancels a give . /// - /// The to cancel + /// The to cancel. /// The who cancelled the . If the TGS user will be used. - /// If the operation should wait until the job exits before completing - /// The for the operation + /// If the operation should wait until the job exits before completing. + /// The for the operation. /// A resulting in the updated if it was cancelled, if it couldn't be found. Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Jobs/JobEntrypoint.cs b/src/Tgstation.Server.Host/Jobs/JobEntrypoint.cs index 0c1f9fc16f..b2bcc3bc2d 100644 --- a/src/Tgstation.Server.Host/Jobs/JobEntrypoint.cs +++ b/src/Tgstation.Server.Host/Jobs/JobEntrypoint.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; diff --git a/src/Tgstation.Server.Host/Jobs/JobException.cs b/src/Tgstation.Server.Host/Jobs/JobException.cs index b8561ccddc..ccb1234559 100644 --- a/src/Tgstation.Server.Host/Jobs/JobException.cs +++ b/src/Tgstation.Server.Host/Jobs/JobException.cs @@ -1,10 +1,11 @@ using System; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Jobs { /// - /// Operation exceptions thrown from the context of a + /// Operation exceptions thrown from the context of a . /// public sealed class JobException : Exception { @@ -14,31 +15,31 @@ namespace Tgstation.Server.Host.Jobs public ErrorCode? ErrorCode { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// public JobException() { } /// - /// Construct a with a + /// Initializes a new instance of the class. /// - /// The message for the + /// The message for the . public JobException(string message) : base(message) { } /// - /// Construct a with a and + /// Initializes a new instance of the class. /// - /// The message for the - /// The inner for the nase + /// The message for the . + /// The inner for the nase . public JobException(string message, Exception innerException) : base(message, innerException) { } /// - /// Construct a with a . + /// Initializes a new instance of the class. /// /// The associated . public JobException(ErrorCode errorCode) : base(errorCode.Describe()) @@ -47,10 +48,10 @@ namespace Tgstation.Server.Host.Jobs } /// - /// Construct a with a and . + /// Initializes a new instance of the class. /// /// The associated . - /// The inner for the nase + /// The inner for the nase . public JobException(ErrorCode errorCode, Exception innerException) : base(errorCode.Describe(), innerException) { ErrorCode = errorCode; diff --git a/src/Tgstation.Server.Host/Jobs/JobHandler.cs b/src/Tgstation.Server.Host/Jobs/JobHandler.cs index c6b923033c..b3e40be60d 100644 --- a/src/Tgstation.Server.Host/Jobs/JobHandler.cs +++ b/src/Tgstation.Server.Host/Jobs/JobHandler.cs @@ -1,32 +1,33 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Extensions; namespace Tgstation.Server.Host.Jobs { /// - /// Class for pairing s with s + /// Class for pairing s with s. /// sealed class JobHandler : IDisposable { /// - /// The for + /// The for . /// readonly CancellationTokenSource cancellationTokenSource; /// - /// A taking a and returning a that the will wrap + /// A taking a and returning a that the will wrap. /// readonly Func jobActivator; /// - /// The being run + /// The being run. /// Task task; /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . public JobHandler(Func jobActivator) @@ -39,15 +40,15 @@ namespace Tgstation.Server.Host.Jobs public void Dispose() => cancellationTokenSource.Dispose(); /// - /// The progress of the job + /// The progress of the job. /// public int? Progress { get; set; } /// - /// Wait for to complete + /// Wait for to complete. /// - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. public Task Wait(CancellationToken cancellationToken) { if (task == null) @@ -57,7 +58,7 @@ namespace Tgstation.Server.Host.Jobs } /// - /// Cancels + /// Cancels . /// public void Cancel() => cancellationTokenSource.Cancel(); diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index 52e068371e..c2f611d619 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -1,11 +1,13 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Serilog.Context; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Serilog.Context; + using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Extensions; @@ -17,12 +19,12 @@ namespace Tgstation.Server.Host.Jobs sealed class JobManager : IJobManager, IDisposable { /// - /// The for the + /// The for the . /// readonly IDatabaseContextFactory databaseContextFactory; /// - /// The for the + /// The for the . /// readonly ILogger logger; @@ -32,7 +34,7 @@ namespace Tgstation.Server.Host.Jobs readonly Lazy instanceCoreProvider; /// - /// of s to running s + /// of s to running s. /// readonly Dictionary jobs; @@ -47,11 +49,11 @@ namespace Tgstation.Server.Host.Jobs readonly object synchronizationLock; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . /// The value of . - /// The value of + /// The value of . public JobManager(IDatabaseContextFactory databaseContextFactory, Lazy instanceCoreProvider, ILogger logger) { this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -69,104 +71,6 @@ namespace Tgstation.Server.Host.Jobs job.Value.Dispose(); } - /// - /// Gets the for a given if it exists - /// - /// The to get the for - /// The - JobHandler CheckGetJob(Job job) - { - lock (synchronizationLock) - { - if (!jobs.TryGetValue(job.Id.Value, out JobHandler jobHandler)) - throw new InvalidOperationException("Job not running!"); - return jobHandler; - } - } - - /// - /// Runner for s - /// - /// The being run - /// The for the - /// The for the operation - /// A representing the running operation - async Task RunJob(Job job, JobEntrypoint operation, CancellationToken cancellationToken) - { - using (LogContext.PushProperty("Job", job.Id)) - try - { - void LogException(Exception ex) => logger.LogDebug(ex, "Job {0} exited with error!", job.Id); - try - { - var oldJob = job; - job = new Job { Id = oldJob.Id }; - - void UpdateProgress(int progress) - { - lock (synchronizationLock) - if (jobs.TryGetValue(oldJob.Id.Value, out var handler)) - handler.Progress = progress; - } - - await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false); - - logger.LogTrace("Starting job..."); - await operation( - instanceCoreProvider.Value.GetInstance(oldJob.Instance), - databaseContextFactory, - job, - UpdateProgress, - cancellationToken) - .ConfigureAwait(false); - - logger.LogDebug("Job {0} completed!", job.Id); - } - catch (OperationCanceledException ex) - { - logger.LogDebug(ex, "Job {0} cancelled!", job.Id); - job.Cancelled = true; - } - catch (JobException e) - { - job.ErrorCode = e.ErrorCode; - job.ExceptionDetails = String.IsNullOrWhiteSpace(e.Message) ? e.InnerException?.Message : e.Message; - LogException(e); - } - catch (Exception e) - { - job.ExceptionDetails = e.ToString(); - LogException(e); - } - - await databaseContextFactory.UseContext(async databaseContext => - { - var attachedJob = new Job - { - Id = job.Id - }; - - databaseContext.Jobs.Attach(attachedJob); - attachedJob.StoppedAt = DateTimeOffset.UtcNow; - attachedJob.ExceptionDetails = job.ExceptionDetails; - attachedJob.ErrorCode = job.ErrorCode; - attachedJob.Cancelled = job.Cancelled; - - // DCT: Cancellation token is for job, operation should always run - await databaseContext.Save(default).ConfigureAwait(false); - }).ConfigureAwait(false); - } - finally - { - lock (synchronizationLock) - { - var handler = jobs[job.Id.Value]; - jobs.Remove(job.Id.Value); - handler.Dispose(); - } - } - } - /// public Task RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext( @@ -182,7 +86,7 @@ namespace Tgstation.Server.Host.Jobs job.Instance = new Models.Instance { - Id = job.Instance.Id + Id = job.Instance.Id, }; databaseContext.Instances.Attach(job.Instance); @@ -194,7 +98,7 @@ namespace Tgstation.Server.Host.Jobs else job.StartedBy = new User { - Id = job.StartedBy.Id + Id = job.StartedBy.Id, }; databaseContext.Users.Attach(job.StartedBy); @@ -223,19 +127,19 @@ namespace Tgstation.Server.Host.Jobs => databaseContextFactory.UseContext(async databaseContext => { // mark all jobs as cancelled - var badJobs = await databaseContext + var badJobIds = await databaseContext .Jobs .AsQueryable() .Where(y => !y.StoppedAt.HasValue) .Select(y => y.Id) .ToListAsync(cancellationToken) .ConfigureAwait(false); - if (badJobs.Count > 0) + if (badJobIds.Count > 0) { - logger.LogTrace("Cleaning {0} unfinished jobs...", badJobs.Count); - foreach (var I in badJobs) + logger.LogTrace("Cleaning {0} unfinished jobs...", badJobIds.Count); + foreach (var badJobId in badJobIds) { - var job = new Job { Id = I }; + var job = new Job { Id = badJobId }; databaseContext.Jobs.Attach(job); job.Cancelled = true; job.StoppedAt = DateTimeOffset.UtcNow; @@ -341,5 +245,103 @@ namespace Tgstation.Server.Host.Jobs logger.LogTrace("Activating job manager..."); activationTcs.SetResult(null); } + + /// + /// Gets the for a given if it exists. + /// + /// The to get the for. + /// The . + JobHandler CheckGetJob(Job job) + { + lock (synchronizationLock) + { + if (!jobs.TryGetValue(job.Id.Value, out JobHandler jobHandler)) + throw new InvalidOperationException("Job not running!"); + return jobHandler; + } + } + + /// + /// Runner for s. + /// + /// The being run. + /// The for the . + /// The for the operation. + /// A representing the running operation. + async Task RunJob(Job job, JobEntrypoint operation, CancellationToken cancellationToken) + { + using (LogContext.PushProperty("Job", job.Id)) + try + { + void LogException(Exception ex) => logger.LogDebug(ex, "Job {0} exited with error!", job.Id); + try + { + var oldJob = job; + job = new Job { Id = oldJob.Id }; + + void UpdateProgress(int progress) + { + lock (synchronizationLock) + if (jobs.TryGetValue(oldJob.Id.Value, out var handler)) + handler.Progress = progress; + } + + await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false); + + logger.LogTrace("Starting job..."); + await operation( + instanceCoreProvider.Value.GetInstance(oldJob.Instance), + databaseContextFactory, + job, + UpdateProgress, + cancellationToken) + .ConfigureAwait(false); + + logger.LogDebug("Job {0} completed!", job.Id); + } + catch (OperationCanceledException ex) + { + logger.LogDebug(ex, "Job {0} cancelled!", job.Id); + job.Cancelled = true; + } + catch (JobException e) + { + job.ErrorCode = e.ErrorCode; + job.ExceptionDetails = String.IsNullOrWhiteSpace(e.Message) ? e.InnerException?.Message : e.Message; + LogException(e); + } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + LogException(e); + } + + await databaseContextFactory.UseContext(async databaseContext => + { + var attachedJob = new Job + { + Id = job.Id, + }; + + databaseContext.Jobs.Attach(attachedJob); + attachedJob.StoppedAt = DateTimeOffset.UtcNow; + attachedJob.ExceptionDetails = job.ExceptionDetails; + attachedJob.ErrorCode = job.ErrorCode; + attachedJob.Cancelled = job.Cancelled; + + // DCT: Cancellation token is for job, operation should always run + await databaseContext.Save(default).ConfigureAwait(false); + }).ConfigureAwait(false); + } + finally + { + lock (synchronizationLock) + { + var handler = jobs[job.Id.Value]; + jobs.Remove(job.Id.Value); + handler.Dispose(); + } + } + } } } diff --git a/src/Tgstation.Server.Host/Models/ChatBot.cs b/src/Tgstation.Server.Host/Models/ChatBot.cs index 23f5d380d3..f19b724cd0 100644 --- a/src/Tgstation.Server.Host/Models/ChatBot.cs +++ b/src/Tgstation.Server.Host/Models/ChatBot.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models @@ -14,18 +15,18 @@ namespace Tgstation.Server.Host.Models public const ushort DefaultChannelLimit = 100; /// - /// The instance + /// The instance . /// public long InstanceId { get; set; } /// - /// The parent + /// The parent . /// [Required] public Instance Instance { get; set; } /// - /// See + /// See . /// public ICollection Channels { get; set; } @@ -39,7 +40,7 @@ namespace Tgstation.Server.Host.Models Id = Id, Name = Name, ChannelLimit = ChannelLimit, - ReconnectionInterval = ReconnectionInterval + ReconnectionInterval = ReconnectionInterval, }; } } diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 7282f8d550..59a903f5a4 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -1,15 +1,15 @@ -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { /// public sealed class ChatChannel : Api.Models.ChatChannel, IApiTransformable { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The + /// The . /// public long ChatSettingsId { get; set; } @@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.Models IsWatchdogChannel = IsWatchdogChannel, IsUpdatesChannel = IsUpdatesChannel, IrcChannel = IrcChannel, - Tag = Tag + Tag = Tag, }; } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 6e37158c0a..bb49025ecd 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -9,24 +10,24 @@ namespace Tgstation.Server.Host.Models public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiTransformable { /// - /// See + /// See . /// [Required] public Job Job { get; set; } /// - /// The of + /// The of . /// public long JobId { get; set; } /// - /// See + /// See . /// [Required] public RevisionInformation RevisionInformation { get; set; } /// - /// The the was made with in string form + /// The the was made with in string form. /// [Required] public string ByondVersion { get; set; } @@ -71,6 +72,7 @@ namespace Tgstation.Server.Host.Models return new Version(DMApiMajorVersion.Value, DMApiMinorVersion.Value, DMApiPatchVersion.Value); } + set { DMApiMajorVersion = value?.Major; diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index d7b49c1c76..5a26391015 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -6,17 +6,17 @@ namespace Tgstation.Server.Host.Models public sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The + /// The . /// public long InstanceId { get; set; } /// - /// The parent + /// The parent . /// [Required] public Instance Instance { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 742070d453..a7372e7b16 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models @@ -7,17 +8,17 @@ namespace Tgstation.Server.Host.Models public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings, IApiTransformable { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The instance + /// The instance . /// public long InstanceId { get; set; } /// - /// The parent + /// The parent . /// [Required] public Instance Instance { get; set; } @@ -28,7 +29,7 @@ namespace Tgstation.Server.Host.Models ProjectName = ProjectName, ApiValidationPort = ApiValidationPort, ApiValidationSecurityLevel = ApiValidationSecurityLevel, - RequireDMApiValidation = RequireDMApiValidation + RequireDMApiValidation = RequireDMApiValidation, }; } } diff --git a/src/Tgstation.Server.Host/Models/IApiTransformable.cs b/src/Tgstation.Server.Host/Models/IApiTransformable.cs index 282e0d2442..e2b807b62e 100644 --- a/src/Tgstation.Server.Host/Models/IApiTransformable.cs +++ b/src/Tgstation.Server.Host/Models/IApiTransformable.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { /// /// Represents a host-side model that may be transformed into a . diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 0072175b8a..33cebaf9df 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models @@ -14,17 +15,17 @@ namespace Tgstation.Server.Host.Models public const ushort DefaultChatBotLimit = 10; /// - /// The for the + /// The for the . /// public DreamMakerSettings DreamMakerSettings { get; set; } /// - /// The for the + /// The for the . /// public DreamDaemonSettings DreamDaemonSettings { get; set; } /// - /// The for the + /// The for the . /// public RepositorySettings RepositorySettings { get; set; } @@ -34,22 +35,22 @@ namespace Tgstation.Server.Host.Models public string SwarmIdentifer { get; set; } /// - /// The s in the + /// The s in the . /// public ICollection InstancePermissionSets { get; set; } /// - /// The s for the + /// The s for the . /// public ICollection ChatSettings { get; set; } /// - /// The s in the + /// The s in the . /// public ICollection RevisionInformations { get; set; } /// - /// The s in the + /// The s in the . /// public ICollection Jobs { get; set; } @@ -62,7 +63,7 @@ namespace Tgstation.Server.Host.Models Name = Name, Path = Path, Online = Online, - ChatBotLimit = ChatBotLimit + ChatBotLimit = ChatBotLimit, }; } } diff --git a/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs b/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs index 34bfb98f23..4c415e1067 100644 --- a/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs +++ b/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models @@ -7,12 +8,12 @@ namespace Tgstation.Server.Host.Models public sealed class InstancePermissionSet : Api.Models.Internal.InstancePermissionSet, IApiTransformable { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The of + /// The of . /// public long InstanceId { get; set; } @@ -38,7 +39,7 @@ namespace Tgstation.Server.Host.Models DreamMakerRights = DreamMakerRights, RepositoryRights = RepositoryRights, InstancePermissionSetRights = InstancePermissionSetRights, - PermissionSetId = PermissionSetId + PermissionSetId = PermissionSetId, }; } } diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 29203669cb..d956711281 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -1,26 +1,27 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models { /// - #pragma warning disable CA1724 // naming conflict with gitlab package +#pragma warning disable CA1724 // naming conflict with gitlab package public sealed class Job : Api.Models.Internal.Job, IApiTransformable - #pragma warning restore CA1724 +#pragma warning restore CA1724 { /// - /// See + /// See . /// [Required] public User StartedBy { get; set; } /// - /// See + /// See . /// public User CancelledBy { get; set; } /// - /// The the job belongs to if any + /// The the job belongs to if any. /// [Required] public Instance Instance { get; set; } @@ -38,7 +39,7 @@ namespace Tgstation.Server.Host.Models Description = Description, ExceptionDetails = ExceptionDetails, ErrorCode = ErrorCode, - StartedBy = StartedBy.CreateUserName() + StartedBy = StartedBy.CreateUserName(), }; } } diff --git a/src/Tgstation.Server.Host/Models/OAuthConnection.cs b/src/Tgstation.Server.Host/Models/OAuthConnection.cs index 441ac0e312..7d8429adf6 100644 --- a/src/Tgstation.Server.Host/Models/OAuthConnection.cs +++ b/src/Tgstation.Server.Host/Models/OAuthConnection.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { /// public sealed class OAuthConnection : Api.Models.OAuthConnection, IApiTransformable @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Models public Api.Models.OAuthConnection ToApi() => new Api.Models.OAuthConnection { Provider = Provider, - ExternalUserId = ExternalUserId + ExternalUserId = ExternalUserId, }; } } diff --git a/src/Tgstation.Server.Host/Models/PermissionSet.cs b/src/Tgstation.Server.Host/Models/PermissionSet.cs index be56725791..021a8239f2 100644 --- a/src/Tgstation.Server.Host/Models/PermissionSet.cs +++ b/src/Tgstation.Server.Host/Models/PermissionSet.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Tgstation.Server.Host.Models { diff --git a/src/Tgstation.Server.Host/Models/ReattachInformation.cs b/src/Tgstation.Server.Host/Models/ReattachInformation.cs index 3861be68a9..e6d714fb43 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformation.cs @@ -3,17 +3,17 @@ namespace Tgstation.Server.Host.Models { /// - /// Database representation of + /// Database representation of . /// public sealed class ReattachInformation : ReattachInformationBase { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The for the + /// The for the . /// [Required] public CompileJob CompileJob { get; set; } diff --git a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs index 893eed1710..70364e2f96 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Globalization; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Session; @@ -8,22 +9,22 @@ using Tgstation.Server.Host.Components.Session; namespace Tgstation.Server.Host.Models { /// - /// Base class for + /// Base class for . /// public abstract class ReattachInformationBase : DMApiParameters { /// - /// The system process ID + /// The system process ID. /// public int ProcessId { get; set; } /// - /// The port DreamDaemon was last listening on + /// The port DreamDaemon was last listening on. /// public ushort Port { get; set; } /// - /// The current DreamDaemon reboot state + /// The current DreamDaemon reboot state. /// public RebootState RebootState { get; set; } @@ -34,14 +35,16 @@ namespace Tgstation.Server.Host.Models public DreamDaemonSecurity? LaunchSecurityLevel { get; set; } /// - /// Construct a + /// Initializes a new instance of the class. /// - protected ReattachInformationBase() { } + protected ReattachInformationBase() + { + } /// - /// Construct a from a given + /// Initializes a new instance of the class. /// - /// The to copy values from + /// The to copy values from. protected ReattachInformationBase(ReattachInformationBase copy) { if (copy == null) diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index c36eac01db..870976f624 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Models @@ -7,17 +8,17 @@ namespace Tgstation.Server.Host.Models public sealed class RepositorySettings : Api.Models.Internal.RepositorySettings, IApiTransformable { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The instance + /// The instance . /// public long InstanceId { get; set; } /// - /// The parent + /// The parent . /// [Required] public Instance Instance { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs b/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs index 826b124780..4328910d0c 100644 --- a/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs +++ b/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs @@ -3,23 +3,23 @@ namespace Tgstation.Server.Host.Models { /// - /// Many to many relationship for and + /// Many to many relationship for and . /// public sealed class RevInfoTestMerge { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The + /// The . /// [Required] public TestMerge TestMerge { get; set; } /// - /// The + /// The . /// [Required] public RevisionInformation RevisionInformation { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index feeced9571..62b1017dd5 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -8,33 +8,33 @@ namespace Tgstation.Server.Host.Models public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, IApiTransformable { /// - /// The row Id + /// The row Id. /// public long Id { get; set; } /// - /// The instance + /// The instance . /// public long InstanceId { get; set; } /// - /// The the belongs to + /// The the belongs to. /// [Required] public Instance Instance { get; set; } /// - /// See + /// See . /// public TestMerge PrimaryTestMerge { get; set; } /// - /// See + /// See . /// public ICollection ActiveTestMerges { get; set; } /// - /// See s made from this + /// See s made from this . /// public ICollection CompileJobs { get; set; } @@ -48,8 +48,8 @@ namespace Tgstation.Server.Host.Models ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(), CompileJobs = CompileJobs.Select(x => new Api.Models.EntityId { - Id = x.Id - }).ToList() + Id = x.Id, + }).ToList(), }; } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 0ee060f536..141637ca22 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models @@ -7,24 +7,24 @@ namespace Tgstation.Server.Host.Models public sealed class TestMerge : Api.Models.Internal.TestMergeApiBase, IApiTransformable { /// - /// See + /// See . /// [Required] public User MergedBy { get; set; } /// - /// The initial the was merged with + /// The initial the was merged with. /// [Required] public RevisionInformation PrimaryRevisionInformation { get; set; } /// - /// Foreign key for + /// Foreign key for . /// public long? PrimaryRevisionInformationId { get; set; } /// - /// All the for the + /// All the for the . /// public ICollection RevisonInformations { get; set; } @@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Models MergedBy = MergedBy.CreateUserName(), Number = Number, TargetCommitSha = TargetCommitSha, - Url = Url + Url = Url, }; } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index c97703fae2..06816aeaa0 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; @@ -16,12 +17,12 @@ namespace Tgstation.Server.Host.Models public const string TgsSystemUserName = "TGS"; /// - /// The hash of the user's password + /// The hash of the user's password. /// public string PasswordHash { get; set; } /// - /// See + /// See . /// public User CreatedBy { get; set; } @@ -41,29 +42,29 @@ namespace Tgstation.Server.Host.Models public PermissionSet PermissionSet { get; set; } /// - /// The uppercase invariant of + /// The uppercase invariant of . /// [Required] [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] public string CanonicalName { get; set; } /// - /// When was last changed + /// When was last changed. /// public DateTimeOffset? LastPasswordUpdate { get; set; } /// - /// s created by this + /// s created by this . /// public ICollection CreatedUsers { get; set; } /// - /// The s made by the + /// The s made by the . /// public ICollection TestMerges { get; set; } /// - /// The s made by the + /// The s made by the . /// public ICollection OAuthConnections { get; set; } @@ -74,6 +75,9 @@ namespace Tgstation.Server.Host.Models /// The . public static string CanonicalizeName(string name) => name?.ToUpperInvariant() ?? throw new ArgumentNullException(nameof(name)); + /// + public UserResponse ToApi() => CreateUserResponse(true); + /// /// Generate a from . /// @@ -95,8 +99,5 @@ namespace Tgstation.Server.Host.Models result.PermissionSet = PermissionSet?.ToApi(); return result; } - - /// - public UserResponse ToApi() => CreateUserResponse(true); } } diff --git a/src/Tgstation.Server.Host/Models/UserGroup.cs b/src/Tgstation.Server.Host/Models/UserGroup.cs index 46c047df03..cce61e9229 100644 --- a/src/Tgstation.Server.Host/Models/UserGroup.cs +++ b/src/Tgstation.Server.Host/Models/UserGroup.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index 90fe18ec03..96b74f796d 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -7,25 +7,24 @@ namespace Tgstation.Server.Host /// /// Native Windows methods used by the code. /// - #pragma warning disable SA1600 - #pragma warning disable SA1602 - #pragma warning disable SA1611 - #pragma warning disable SA1615 +#pragma warning disable SA1602 +#pragma warning disable SA1611 +#pragma warning disable SA1615 static class NativeMethods { /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinka#parameters + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinka#parameters. /// [Flags] public enum CreateSymbolicLinkFlags : int { None = 0, Directory = 1, - AllowUnprivilegedCreate = 2 + AllowUnprivilegedCreate = 2, } /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx. /// public enum ThreadAccess : int { @@ -33,7 +32,7 @@ namespace Tgstation.Server.Host } /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type + /// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type. /// [Flags] public enum MiniDumpType : uint @@ -46,78 +45,78 @@ namespace Tgstation.Server.Host } /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid. /// [DllImport("user32.dll")] public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-findwindoww + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-findwindoww. /// [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage. /// [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); /// - /// See https://msdn.microsoft.com/en-us/library/ms633493(v=VS.85).aspx + /// See https://msdn.microsoft.com/en-us/library/ms633493(v=VS.85).aspx. /// public delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam); /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumchildwindows + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumchildwindows. /// [DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam); /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowtextw + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowtextw. /// [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx. /// [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); /// - /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinkw + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinkw. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, CreateSymbolicLinkFlags dwFlags); /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684335(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684335(v=vs.85).aspx. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CloseHandle(IntPtr hObject); /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686345(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686345(v=vs.85).aspx. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern uint SuspendThread(IntPtr hThread); /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms685086(v=vs.85).aspx + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms685086(v=vs.85).aspx. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern uint ResumeThread(IntPtr hThread); /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump + /// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump. /// [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool MiniDumpWriteDump( diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index ffe02ba262..9b673bb8db 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; + using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Properties; using Tgstation.Server.Host.System; @@ -11,7 +12,7 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host { /// - /// Entrypoint for the + /// Entrypoint for the . /// static class Program { @@ -23,12 +24,14 @@ namespace Tgstation.Server.Host /// /// The to use. /// +#pragma warning disable SA1401 // Fields should be private internal static IServerFactory ServerFactory = Application.CreateDefaultServerFactory(); +#pragma warning restore SA1401 // Fields should be private /// - /// Entrypoint for the + /// Entrypoint for the . /// - /// The command line arguments + /// The command line arguments. /// The . public static async Task Main(string[] args) { @@ -74,7 +77,10 @@ namespace Tgstation.Server.Host { await server.Run(cancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + } + return server.RestartRequested ? 1 : 0; } catch (Exception e) diff --git a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs index d6552a90d2..14ced87284 100644 --- a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs +++ b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; namespace Tgstation.Server.Host.Properties @@ -37,12 +37,12 @@ namespace Tgstation.Server.Host.Properties public string RawHostWatchdogVersion { get; } /// - /// The compile time default + /// The compile time default. /// public string DefaultControlPanelChannel { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index a03772aa4f..510a882004 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; + using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; @@ -21,17 +22,19 @@ namespace Tgstation.Server.Host.Security public ISystemIdentity SystemIdentity { get; } /// - /// Construct an empty + /// Initializes a new instance of the class. /// - public AuthenticationContext() { } + public AuthenticationContext() + { + } /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstancePermissionSet instanceUser) + /// The value of . + /// The value of . + /// The value of . + public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstancePermissionSet instanceUser) { User = user ?? throw new ArgumentNullException(nameof(user)); if (systemIdentity == null && User.SystemIdentifier != null) diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index be393e8db9..c7e69f579c 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -1,10 +1,12 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; @@ -18,12 +20,12 @@ namespace Tgstation.Server.Host.Security public IAuthenticationContext CurrentAuthenticationContext { get; private set; } /// - /// The for the + /// The for the . /// readonly IDatabaseContext databaseContext; /// - /// The for the + /// The for the . /// readonly IIdentityCache identityCache; @@ -38,10 +40,10 @@ namespace Tgstation.Server.Host.Security readonly SwarmConfiguration swarmConfiguration; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . /// The containing the value of . /// The value of . public AuthenticationContextFactory( diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs index b370cb62a3..f0407a0a93 100644 --- a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs @@ -1,12 +1,14 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; + using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; @@ -16,14 +18,14 @@ namespace Tgstation.Server.Host.Security sealed class ClaimsInjector : IClaimsInjector { /// - /// The for the + /// The for the . /// readonly IAuthenticationContextFactory authenticationContextFactory; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public ClaimsInjector(IAuthenticationContextFactory authenticationContextFactory) { this.authenticationContextFactory = authenticationContextFactory ?? throw new ArgumentNullException(nameof(authenticationContextFactory)); @@ -73,20 +75,20 @@ namespace Tgstation.Server.Host.Security var enumerator = Enum.GetValues(typeof(RightsType)); var claims = new List(); - foreach (RightsType I in enumerator) + foreach (RightsType rightType in enumerator) { // if there's no instance user, do a weird thing and add all the instance roles // we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid // if user is null that means they got the token with an expired password var rightAsULong = authenticationContext.User == null - || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstancePermissionSet == null) + || (RightsHelper.IsInstanceRight(rightType) && authenticationContext.InstancePermissionSet == null) ? ~0UL - : authenticationContext.GetRight(I); - var rightEnum = RightsHelper.RightToType(I); + : authenticationContext.GetRight(rightType); + var rightEnum = RightsHelper.RightToType(rightType); var right = (Enum)Enum.ToObject(rightEnum, rightAsULong); - foreach (Enum J in Enum.GetValues(rightEnum)) - if (right.HasFlag(J)) - claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); + foreach (Enum enumeratedRight in Enum.GetValues(rightEnum)) + if (right.HasFlag(enumeratedRight)) + claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(rightType, enumeratedRight))); } tokenValidatedContext.Principal.AddIdentity(new ClaimsIdentity(claims)); diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 491d245ecd..7ad8c61adb 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -1,7 +1,9 @@ -using Microsoft.AspNetCore.Identity; -using System; +using System; using System.Globalization; using System.Security.Cryptography; + +using Microsoft.AspNetCore.Identity; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -10,19 +12,19 @@ namespace Tgstation.Server.Host.Security sealed class CryptographySuite : ICryptographySuite { /// - /// Length in s of generated base64 secure string + /// Length in s of generated base64 secure string. /// const uint SecureStringLength = 30; /// - /// The for the + /// The for the . /// readonly IPasswordHasher passwordHasher; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public CryptographySuite(IPasswordHasher passwordHasher) { this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index 9c9962ccd8..55e68a4cc1 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; @@ -10,7 +11,7 @@ namespace Tgstation.Server.Host.Security public interface IAuthenticationContext : IDisposable { /// - /// The authenticated user + /// The authenticated user. /// User User { get; } @@ -25,14 +26,14 @@ namespace Tgstation.Server.Host.Security InstancePermissionSet InstancePermissionSet { get; } /// - /// Get the value of a given + /// Get the value of a given . /// - /// The of the right to get - /// The value of . Note that if is all based rights will return 0 + /// The of the right to get. + /// The value of . Note that if is all based rights will return 0. ulong GetRight(RightsType rightsType); /// - /// The of if applicable + /// The of if applicable. /// ISystemIdentity SystemIdentity { get; } } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index bc673770ad..d74d9529c6 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -1,27 +1,27 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Security { /// - /// For creating and accessing authentication contexts + /// For creating and accessing authentication contexts. /// public interface IAuthenticationContextFactory { /// - /// The the created + /// The the created. /// IAuthenticationContext CurrentAuthenticationContext { get; } /// - /// Create an to populate + /// Create an to populate . /// - /// The of the - /// The of the operation - /// The the resulting 's password must be valid after - /// The for the operation - /// A representing the running operation + /// The of the . + /// The of the operation. + /// The the resulting 's password must be valid after. + /// The for the operation. + /// A representing the running operation. Task CreateAuthenticationContext(long userId, long? instanceId, DateTimeOffset validAfter, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs index f5608a6b21..52dbebe6af 100644 --- a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs @@ -1,20 +1,21 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using System.Threading; +using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; + namespace Tgstation.Server.Host.Security { /// - /// For injecting s that can look for + /// For injecting s that can look for. /// interface IClaimsInjector { /// - /// Setup the s for a given + /// Setup the s for a given . /// - /// The containing the and of the request and the to add s to - /// The for the operation - /// A representing the running operation + /// The containing the and of the request and the to add s to. + /// The for the operation. + /// A representing the running operation. Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs index 540fb60e9f..257a78f4d1 100644 --- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -3,37 +3,37 @@ namespace Tgstation.Server.Host.Security { /// - /// Contains various cryptographic functions + /// Contains various cryptographic functions. /// public interface ICryptographySuite { /// - /// Generates a secure set of s + /// Generates a secure set of s. /// - /// The amount of s to generate - /// A secure set of s + /// The amount of s to generate. + /// A secure set of s. byte[] GetSecureBytes(uint amount); /// - /// Sets a for a given + /// Sets a for a given . /// - /// The whos is to be set - /// The new password for the - /// If the is just being created + /// The whos is to be set. + /// The new password for the . + /// If the is just being created. void SetUserPassword(User user, string newPassword, bool newUser); /// - /// Checks a given matches a given 's . This may result in being modified and this should be persisted + /// Checks a given matches a given 's . This may result in being modified and this should be persisted. /// - /// The to check - /// The password to check - /// if matches the hash, otherwise + /// The to check. + /// The password to check. + /// if matches the hash, otherwise. bool CheckUserPassword(User user, string password); /// - /// Generates a 40-length secure ascii + /// Generates a 40-length secure ascii . /// - /// A 40-length secure ascii + /// A 40-length secure ascii . string GetSecureString(); } } diff --git a/src/Tgstation.Server.Host/Security/IIdentityCache.cs b/src/Tgstation.Server.Host/Security/IIdentityCache.cs index b5a9fed42c..0832189f83 100644 --- a/src/Tgstation.Server.Host/Security/IIdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IIdentityCache.cs @@ -1,26 +1,27 @@ using System; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - /// For caching s + /// For caching s. /// public interface IIdentityCache { /// - /// Keep a 's alive until an time + /// Keep a 's alive until an time. /// - /// The the belongs to - /// The to cache - /// When the should expire + /// The the belongs to. + /// The to cache. + /// When the should expire. void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); /// - /// Attempt to load a cached + /// Attempt to load a cached . /// - /// The the belongs to - /// The cached or if it doesn't exist or expired + /// The the belongs to. + /// The cached or if it doesn't exist or expired. ISystemIdentity LoadCachedIdentity(User user); } } diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs index bd880617bc..bd63d784de 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -5,17 +5,17 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Security { /// - /// Represents a user on the current + /// Represents a user on the current . /// public interface ISystemIdentity : IDisposable { /// - /// A unique identifier for the user + /// A unique identifier for the user. /// string Uid { get; } /// - /// The user's name + /// The user's name. /// string Username { get; } @@ -25,17 +25,17 @@ namespace Tgstation.Server.Host.Security bool CanCreateSymlinks { get; } /// - /// Clone the creating another copy that must have called on it + /// Clone the creating another copy that must have called on it. /// - /// A new mirroring the current one + /// A new mirroring the current one. ISystemIdentity Clone(); /// - /// Runs a given in the context of the + /// Runs a given in the context of the . /// - /// The to perform, should be simple and not use any s or threading - /// The for the operation - /// A representing the running operation + /// The to perform, should be simple and not use any s or threading. + /// The for the operation. + /// A representing the running operation. Task RunImpersonated(Action action, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index 06371c4fe6..0a8f187429 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -1,11 +1,12 @@ using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - /// Factory for s + /// Factory for s. /// public interface ISystemIdentityFactory { @@ -16,20 +17,20 @@ namespace Tgstation.Server.Host.Security ISystemIdentity GetCurrent(); /// - /// Create a for a given + /// Create a for a given . /// - /// The user to create a for - /// The for the operation - /// A new or if the has no + /// The user to create a for. + /// The for the operation. + /// A new or if the has no . Task CreateSystemIdentity(User user, CancellationToken cancellationToken); /// - /// Create a for a given username and password + /// Create a for a given username and password. /// - /// The username of the user - /// The password of the user - /// The for the operation - /// A new + /// The username of the user. + /// The password of the user. + /// The for the operation. + /// A new . Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index 58b7e02853..28e9273b46 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -1,27 +1,29 @@ -using Microsoft.IdentityModel.Tokens; -using System.Threading; +using System.Threading; using System.Threading.Tasks; + +using Microsoft.IdentityModel.Tokens; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Security { /// - /// For creating s + /// For creating s. /// public interface ITokenFactory { /// - /// The for the + /// The for the . /// TokenValidationParameters ValidationParameters { get; } /// - /// Create a for a given + /// Create a for a given . /// /// The to create the token for. Must have the field available. /// Whether or not this is an OAuth login. - /// The for the operation - /// A resulting in a new + /// The for the operation. + /// A resulting in a new . Task CreateToken(Models.User user, bool oAuth, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index 7b39d8294c..c55db5f676 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; @@ -11,25 +13,25 @@ namespace Tgstation.Server.Host.Security sealed class IdentityCache : IIdentityCache, IDisposable { /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The map of s to s + /// The map of s to s. /// readonly Dictionary cachedIdentities; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public IdentityCache(IAsyncDelayer asyncDelayer, ILogger logger) { this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); @@ -42,8 +44,8 @@ namespace Tgstation.Server.Host.Security public void Dispose() { logger.LogTrace("Disposing..."); - foreach (var I in cachedIdentities.Select(x => x.Value).ToList()) - I.Dispose(); + foreach (var cachedIdentity in cachedIdentities.Select(x => x.Value).ToList()) + cachedIdentity.Dispose(); } /// @@ -53,6 +55,7 @@ namespace Tgstation.Server.Host.Security throw new ArgumentNullException(nameof(user)); if (systemIdentity == null) throw new ArgumentNullException(nameof(systemIdentity)); + lock (cachedIdentities) { var uid = systemIdentity.Uid; @@ -64,12 +67,16 @@ namespace Tgstation.Server.Host.Security identCache.Dispose(); // also clears it out } - identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () => - { - logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id); - lock (cachedIdentities) - cachedIdentities.Remove(user.Id.Value); - }, expiry); + identCache = new IdentityCacheObject( + systemIdentity.Clone(), + asyncDelayer, + () => + { + logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id); + lock (cachedIdentities) + cachedIdentities.Remove(user.Id.Value); + }, + expiry); cachedIdentities.Add(user.Id.Value, identCache); } } diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs index 1684935a6c..8495caf275 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs @@ -1,37 +1,38 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Security { /// - /// For keeping a specific alive for a period of time + /// For keeping a specific alive for a period of time. /// sealed class IdentityCacheObject : IDisposable { /// - /// The the manages + /// The the manages. /// public ISystemIdentity SystemIdentity { get; } /// - /// The for the + /// The for the . /// readonly CancellationTokenSource cancellationTokenSource; /// - /// The to clean up + /// The to clean up . /// readonly Task task; /// - /// Construct an + /// Initializes a new instance of the class. /// - /// The value of - /// The used to delay the expiry - /// The to take on expiry - /// The + /// The value of . + /// The used to delay the expiry. + /// The to take on expiry. + /// The . public IdentityCacheObject(ISystemIdentity systemIdentity, IAsyncDelayer asyncDelayer, Action onExpiry, DateTimeOffset expiry) { SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); @@ -71,7 +72,10 @@ namespace Tgstation.Server.Host.Security { task.GetAwaiter().GetResult(); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + } + cancellationTokenSource.Dispose(); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs index 4cb1995f59..9040b6dd5a 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs @@ -1,12 +1,14 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; +using System; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -49,12 +51,12 @@ namespace Tgstation.Server.Host.Security.OAuth { ContractResolver = new DefaultContractResolver { - NamingStrategy = new SnakeCaseNamingStrategy() - } + NamingStrategy = new SnakeCaseNamingStrategy(), + }, }; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs index 37d055d08b..d28bc9d04f 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs @@ -1,6 +1,8 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Net.Http; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -13,7 +15,7 @@ namespace Tgstation.Server.Host.Security.OAuth sealed class DiscordOAuthValidator : GenericOAuthValidator { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . diff --git a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs index 4a6beb15e6..1fe959385c 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs @@ -1,12 +1,14 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; @@ -30,11 +32,11 @@ namespace Tgstation.Server.Host.Security.OAuth protected abstract Uri UserInformationUrl { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The for the + /// The for the . + /// The for the . + /// The for the . /// The for the . public GenericOAuthValidator( IHttpClientFactory httpClientFactory, @@ -49,27 +51,6 @@ namespace Tgstation.Server.Host.Security.OAuth { } - /// - /// Decode the token payload . - /// - /// The token payload . - /// The OAuth2 bearer access token on success, otherwise. - protected abstract string DecodeTokenPayload(dynamic responseJson); - - /// - /// Decode the user information payload . - /// - /// The user information payload . - /// The user ID on success, otherwise. - protected abstract string DecodeUserInformationPayload(dynamic responseJson); - - /// - /// Create the for a given - /// - /// The OAuth code from the browser. - /// The to send to . - protected abstract OAuthTokenRequest CreateTokenRequest(string code); - /// public override async Task ValidateResponseCode(string code, CancellationToken cancellationToken) { @@ -128,7 +109,28 @@ namespace Tgstation.Server.Host.Security.OAuth { ClientId = OAuthConfiguration.ClientId, RedirectUri = OAuthConfiguration.RedirectUrl, - ServerUrl = OAuthConfiguration.ServerUrl + ServerUrl = OAuthConfiguration.ServerUrl, }); + + /// + /// Decode the token payload . + /// + /// The token payload . + /// The OAuth2 bearer access token on success, otherwise. + protected abstract string DecodeTokenPayload(dynamic responseJson); + + /// + /// Decode the user information payload . + /// + /// The user information payload . + /// The user ID on success, otherwise. + protected abstract string DecodeUserInformationPayload(dynamic responseJson); + + /// + /// Create the for a given . + /// + /// The OAuth code from the browser. + /// The to send to . + protected abstract OAuthTokenRequest CreateTokenRequest(string code); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs index c678568ebe..f966ccfbe1 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; -using Octokit; -using System; +using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Octokit; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -34,7 +36,7 @@ namespace Tgstation.Server.Host.Security.OAuth readonly OAuthConfiguration oAuthConfiguration; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -67,7 +69,7 @@ namespace Tgstation.Server.Host.Security.OAuth oAuthConfiguration.ClientSecret, code) { - RedirectUri = oAuthConfiguration.RedirectUrl + RedirectUri = oAuthConfiguration.RedirectUrl, }) .ConfigureAwait(false); @@ -89,7 +91,7 @@ namespace Tgstation.Server.Host.Security.OAuth { throw; } - catch(ApiException ex) + catch (ApiException ex) { logger.LogWarning(ex, "API error while completing OAuth handshake!"); return null; @@ -101,7 +103,7 @@ namespace Tgstation.Server.Host.Security.OAuth new OAuthProviderInfo { ClientId = oAuthConfiguration.ClientId, - RedirectUri = oAuthConfiguration.RedirectUrl + RedirectUri = oAuthConfiguration.RedirectUrl, }); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs index 06cae4e96e..4ea0ce2f14 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security.OAuth { /// - /// Contains s + /// Contains s. /// public interface IOAuthProviders { diff --git a/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs index be8df49788..bd3b25e968 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs @@ -1,5 +1,6 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security.OAuth diff --git a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs index 61e08a56ab..87e3cdbce8 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs @@ -1,6 +1,8 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Net.Http; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -27,7 +29,7 @@ namespace Tgstation.Server.Host.Security.OAuth string BaseProtocolPath => $"{OAuthConfiguration.ServerUrl}/protocol/openid-connect"; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The for the . /// The for the . diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs index 0118f18fd6..b51f21a61c 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -1,11 +1,13 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -22,7 +24,7 @@ namespace Tgstation.Server.Host.Security.OAuth readonly IReadOnlyCollection validators; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The to use. /// The to use. diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs index eb715953c6..9256092f10 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs @@ -1,4 +1,5 @@ -using System; +using System; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Security.OAuth @@ -29,11 +30,11 @@ namespace Tgstation.Server.Host.Security.OAuth public string GrantType { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The to build from. /// The value of . - /// The value of + /// The value of . public OAuthTokenRequest(OAuthConfiguration oAuthConfiguration, string code, string scope) : base(oAuthConfiguration) { diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGBaseResponse.cs b/src/Tgstation.Server.Host/Security/OAuth/TGBaseResponse.cs index 1f887179e3..0dd50cf814 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGBaseResponse.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGBaseResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Security.OAuth +namespace Tgstation.Server.Host.Security.OAuth { /// /// Base for tgstation forum responses. diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGCreateSessionResponse.cs b/src/Tgstation.Server.Host/Security/OAuth/TGCreateSessionResponse.cs index 7d8d53face..a012ada04f 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGCreateSessionResponse.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGCreateSessionResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Security.OAuth +namespace Tgstation.Server.Host.Security.OAuth { /// /// Response when creating a tgstation forums session. diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs index 3024e61001..309c479a07 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -8,6 +6,10 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; + +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -33,11 +35,11 @@ namespace Tgstation.Server.Host.Security.OAuth readonly List> sessions; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The for the - /// The for the - /// The for the + /// The for the . + /// The for the . + /// The for the . /// The for the . public TGForumsOAuthValidator( IHttpClientFactory httpClientFactory, @@ -65,7 +67,7 @@ namespace Tgstation.Server.Host.Security.OAuth { UriBuilder builder = new UriBuilder("https://tgstation13.org/phpBB/oauth_create_session.php") { - Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&return_uri={HttpUtility.UrlEncode(OAuthConfiguration.RedirectUrl.ToString())}" + Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&return_uri={HttpUtility.UrlEncode(OAuthConfiguration.RedirectUrl.ToString())}", }; using var request = new HttpRequestMessage(HttpMethod.Get, builder.Uri); @@ -90,7 +92,7 @@ namespace Tgstation.Server.Host.Security.OAuth return new OAuthProviderInfo { ClientId = newSession.SessionPublicToken, - RedirectUri = OAuthConfiguration.RedirectUrl + RedirectUri = OAuthConfiguration.RedirectUrl, }; } catch (Exception ex) @@ -106,7 +108,7 @@ namespace Tgstation.Server.Host.Security.OAuth try { var sessionTuple = sessions.FirstOrDefault(x => x.Item1.SessionPublicToken == code); - if(sessionTuple == null) + if (sessionTuple == null) { Logger.LogWarning("No known session with this code active!"); return null; @@ -116,7 +118,7 @@ namespace Tgstation.Server.Host.Security.OAuth UriBuilder builder = new UriBuilder("https://tgstation13.org/phpBB/oauth_get_session_info.php") { - Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&session_private_token={HttpUtility.UrlEncode(sessionTuple.Item1.SessionPrivateToken)}" + Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&session_private_token={HttpUtility.UrlEncode(sessionTuple.Item1.SessionPrivateToken)}", }; using var request = new HttpRequestMessage(HttpMethod.Get, builder.Uri); diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGGetSessionInfoResponse.cs b/src/Tgstation.Server.Host/Security/OAuth/TGGetSessionInfoResponse.cs index 89b834e769..62a630a38b 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGGetSessionInfoResponse.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGGetSessionInfoResponse.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Security.OAuth +namespace Tgstation.Server.Host.Security.OAuth { /// /// Response when getting tgstation forum user's info. diff --git a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs index c5a19348a5..e85882665b 100644 --- a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs @@ -1,12 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - /// for posix systems + /// for posix systems. /// /// TODO: Blocked by https://github.com/dotnet/corefx/issues/3187 sealed class PosixSystemIdentityFactory : ISystemIdentityFactory diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index ee37fea604..cfaa93b23a 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -1,11 +1,13 @@ -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using System; +using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; + using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -25,15 +27,15 @@ namespace Tgstation.Server.Host.Security readonly SecurityConfiguration securityConfiguration; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The used for generating the + /// The value of . + /// The used for generating the . /// The used to generate the issuer name. /// The containing the value of . public TokenFactory( @@ -71,7 +73,7 @@ namespace Tgstation.Server.Host.Security RequireSignedTokens = true, - RequireExpirationTime = true + RequireExpirationTime = true, }; } @@ -102,14 +104,14 @@ namespace Tgstation.Server.Host.Security new Claim(JwtRegisteredClaimNames.Exp, expiry.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture)), new Claim(JwtRegisteredClaimNames.Nbf, nowUnix.ToString(CultureInfo.InvariantCulture)), new Claim(JwtRegisteredClaimNames.Iss, ValidationParameters.ValidIssuer), - new Claim(JwtRegisteredClaimNames.Aud, ValidationParameters.ValidAudience) + new Claim(JwtRegisteredClaimNames.Aud, ValidationParameters.ValidAudience), }; var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(ValidationParameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); return new TokenResponse { Bearer = new JwtSecurityTokenHandler().WriteToken(token), - ExpiresAt = expiry + ExpiresAt = expiry, }; } } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index a833dc4d14..6c76bdb650 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -1,14 +1,15 @@ -using System; +using System; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Security { /// - /// for windows systems + /// for windows systems. /// sealed class WindowsSystemIdentity : ISystemIdentity { @@ -22,12 +23,12 @@ namespace Tgstation.Server.Host.Security public bool CanCreateSymlinks => canCreateSymlinks ?? throw new NotSupportedException(); /// - /// The for the + /// The for the . /// readonly WindowsIdentity identity; /// - /// The for the + /// The for the . /// readonly UserPrincipal userPrincipal; @@ -37,9 +38,9 @@ namespace Tgstation.Server.Host.Security readonly bool? canCreateSymlinks; /// - /// Construct a using a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public WindowsSystemIdentity(WindowsIdentity identity) { this.identity = identity ?? throw new ArgumentNullException(nameof(identity)); @@ -47,9 +48,9 @@ namespace Tgstation.Server.Host.Security } /// - /// Construct a using a + /// Initializes a new instance of the class. /// - /// The value of + /// The value of . public WindowsSystemIdentity(UserPrincipal userPrincipal) { this.userPrincipal = userPrincipal ?? throw new ArgumentNullException(nameof(userPrincipal)); @@ -84,13 +85,17 @@ namespace Tgstation.Server.Host.Security } /// - public Task RunImpersonated(Action action, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (action == null) - throw new ArgumentNullException(nameof(action)); - if (identity == null) - throw new InvalidOperationException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); - WindowsIdentity.RunImpersonated(identity.AccessToken, action); - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + public Task RunImpersonated(Action action, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (identity == null) + throw new InvalidOperationException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); + WindowsIdentity.RunImpersonated(identity.AccessToken, action); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index fa5eb5924b..e183c720d8 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -1,31 +1,33 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Win32.SafeHandles; -using System; +using System; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Win32.SafeHandles; + using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - /// for windows systems. Uses long running tasks due to potential networked domains + /// for windows systems. Uses long running tasks due to potential networked domains. /// sealed class WindowsSystemIdentityFactory : ISystemIdentityFactory { /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// Extract the username and domain name from a in the format "username\\domainname" + /// Extract the username and domain name from a in the format "username\\domainname". /// - /// The input - /// The output username - /// The output domain name. May be + /// The input . + /// The output username. + /// The output domain name. May be . static void GetUserAndDomainName(string input, out string username, out string domainName) { var splits = input.Split('\\'); @@ -34,9 +36,9 @@ namespace Tgstation.Server.Host.Security } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of logger + /// The value of logger. public WindowsSystemIdentityFactory(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -46,75 +48,82 @@ namespace Tgstation.Server.Host.Security public ISystemIdentity GetCurrent() => new WindowsSystemIdentity(WindowsIdentity.GetCurrent()); /// - public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (user == null) - throw new ArgumentNullException(nameof(user)); - - if (user.SystemIdentifier == null) - throw new InvalidOperationException("User's SystemIdentifier must not be null!"); - - PrincipalContext pc = null; - UserPrincipal principal = null; - - bool TryGetPrincipalFromContextType(ContextType contextType) + public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - try + if (user == null) + throw new ArgumentNullException(nameof(user)); + + if (user.SystemIdentifier == null) + throw new InvalidOperationException("User's SystemIdentifier must not be null!"); + + PrincipalContext pc = null; + UserPrincipal principal = null; + + bool TryGetPrincipalFromContextType(ContextType contextType) { - pc = new PrincipalContext(contextType); - cancellationToken.ThrowIfCancellationRequested(); - principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - logger.LogWarning(e, "Error loading user for context type {0}!", contextType); - } - finally - { - if (principal == null) + try { - pc?.Dispose(); + pc = new PrincipalContext(contextType); cancellationToken.ThrowIfCancellationRequested(); + principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + logger.LogWarning(e, "Error loading user for context type {0}!", contextType); + } + finally + { + if (principal == null) + { + pc?.Dispose(); + cancellationToken.ThrowIfCancellationRequested(); + } + } + + return principal != null; } - return principal != null; - } - - if (!TryGetPrincipalFromContextType(ContextType.Machine) && !TryGetPrincipalFromContextType(ContextType.Domain)) - return null; - return (ISystemIdentity)new WindowsSystemIdentity(principal); - }, - cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + if (!TryGetPrincipalFromContextType(ContextType.Machine) && !TryGetPrincipalFromContextType(ContextType.Domain)) + return null; + return (ISystemIdentity)new WindowsSystemIdentity(principal); + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); /// - public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - - var originalUsername = username; - GetUserAndDomainName(originalUsername, out username, out var domainName); - - var res = NativeMethods.LogonUser(username, domainName, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token); - if (!res) + public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew( + () => { - logger.LogTrace("Invalid system identity/password combo for username {0}!", originalUsername); - return null; - } + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); - logger.LogTrace("Authenticated username {0} using system identity!", originalUsername); + var originalUsername = username; + GetUserAndDomainName(originalUsername, out username, out var domainName); - // checked internally, windows identity always duplicates the handle when constructed - using var handle = new SafeAccessTokenHandle(token); - return (ISystemIdentity)new WindowsSystemIdentity( - new WindowsIdentity(handle.DangerousGetHandle())); // https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 - }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + var res = NativeMethods.LogonUser(username, domainName, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token); + if (!res) + { + logger.LogTrace("Invalid system identity/password combo for username {0}!", originalUsername); + return null; + } + + logger.LogTrace("Authenticated username {0} using system identity!", originalUsername); + + // checked internally, windows identity always duplicates the handle when constructed + using var handle = new SafeAccessTokenHandle(token); + return (ISystemIdentity)new WindowsSystemIdentity( + new WindowsIdentity(handle.DangerousGetHandle())); // https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 + }, + cancellationToken, + DefaultIOManager.BlockingTaskCreationOptions, + TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 3f98fbe38a..568ed6bd74 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,13 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -33,17 +35,17 @@ namespace Tgstation.Server.Host #endif /// - /// The for the + /// The for the . /// readonly IHostBuilder hostBuilder; /// - /// The s to run when the restarts + /// The s to run when the restarts. /// readonly List restartHandlers; /// - /// The absolute path to install updates to + /// The absolute path to install updates to. /// readonly string updatePath; @@ -58,30 +60,30 @@ namespace Tgstation.Server.Host ISwarmService swarmService; /// - /// The for the + /// The for the . /// ILogger logger; /// - /// The for the + /// The for the . /// GeneralConfiguration generalConfiguration; /// - /// The for the + /// The for the . /// CancellationTokenSource cancellationTokenSource; /// - /// The to propagate when the server terminates + /// The to propagate when the server terminates. /// Exception propagatedException; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public Server(IHostBuilder hostBuilder, string updatePath) { this.hostBuilder = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder)); @@ -93,34 +95,6 @@ namespace Tgstation.Server.Host restartLock = new object(); } - /// - /// Throws an if the cannot be used - /// - /// If should be checked - void CheckSanity(bool checkWatchdog) - { - if (checkWatchdog && !WatchdogPresent && propagatedException == null) - throw new InvalidOperationException("Server restarts are not supported"); - - if (cancellationTokenSource == null || logger == null) - throw new InvalidOperationException("Tried to control a non-running Server!"); - } - - /// - /// Re-throw if it exists - /// - /// An existing that should be thrown as well, but not by itself. - void CheckExceptionPropagation(Exception otherException) - { - if (propagatedException == null) - return; - - if (otherException != null) - throw new AggregateException(propagatedException, otherException); - - throw propagatedException; - } - /// public async Task Run(CancellationToken cancellationToken) { @@ -222,7 +196,7 @@ namespace Tgstation.Server.Host { throw new AggregateException(e1, e2); } - + throw; } @@ -304,13 +278,44 @@ namespace Tgstation.Server.Host /// public Task Restart() => Restart(null, null, true); + /// + public Task Die(Exception exception) => Restart(null, exception, false); + /// - /// Implements + /// Throws an if the cannot be used. /// - /// The of any potential updates being applied - /// The potential value of + /// If should be checked. + void CheckSanity(bool checkWatchdog) + { + if (checkWatchdog && !WatchdogPresent && propagatedException == null) + throw new InvalidOperationException("Server restarts are not supported"); + + if (cancellationTokenSource == null || logger == null) + throw new InvalidOperationException("Tried to control a non-running Server!"); + } + + /// + /// Re-throw if it exists. + /// + /// An existing that should be thrown as well, but not by itself. + void CheckExceptionPropagation(Exception otherException) + { + if (propagatedException == null) + return; + + if (otherException != null) + throw new AggregateException(propagatedException, otherException); + + throw propagatedException; + } + + /// + /// Implements . + /// + /// The of any potential updates being applied. + /// The potential value of . /// If the host watchdog is required for this "restart". - /// A representing the running operation + /// A representing the running operation. async Task Restart(Version newVersion, Exception exception, bool requireWatchdog) { CheckSanity(requireWatchdog); @@ -359,8 +364,5 @@ namespace Tgstation.Server.Host logger.LogTrace("Stopping host..."); cancellationTokenSource.Cancel(); } - - /// - public Task Die(Exception exception) => Restart(null, exception, false); } } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index e739d5c75f..d4ab9fc566 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,12 +1,14 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; @@ -29,7 +31,7 @@ namespace Tgstation.Server.Host public IIOManager IOManager { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -55,6 +57,7 @@ namespace Tgstation.Server.Host builder.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) .AddYamlFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: true); + // reorganize the builder so our yaml configs don't override the env/cmdline configs // values obtained via debugger var environmentJsonConfig = builder.Sources[2]; @@ -62,6 +65,7 @@ namespace Tgstation.Server.Host var cmdLineConfig = builder.Sources[4]; var baseYmlConfig = builder.Sources[5]; var environmentYmlConfig = builder.Sources[6]; + builder.Sources[2] = baseYmlConfig; builder.Sources[3] = environmentJsonConfig; builder.Sources[4] = environmentYmlConfig; diff --git a/src/Tgstation.Server.Host/Setup/IPostSetupServices.cs b/src/Tgstation.Server.Host/Setup/IPostSetupServices.cs index edc85dea40..43ec10a002 100644 --- a/src/Tgstation.Server.Host/Setup/IPostSetupServices.cs +++ b/src/Tgstation.Server.Host/Setup/IPostSetupServices.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Setup diff --git a/src/Tgstation.Server.Host/Setup/IPostSetupServices{TLoggerType}.cs b/src/Tgstation.Server.Host/Setup/IPostSetupServices{TLoggerType}.cs index 029ec74c55..f36d9526e7 100644 --- a/src/Tgstation.Server.Host/Setup/IPostSetupServices{TLoggerType}.cs +++ b/src/Tgstation.Server.Host/Setup/IPostSetupServices{TLoggerType}.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; namespace Tgstation.Server.Host.Setup { diff --git a/src/Tgstation.Server.Host/Setup/PostSetupServices.cs b/src/Tgstation.Server.Host/Setup/PostSetupServices.cs index 1297381ef2..684b1bc1ab 100644 --- a/src/Tgstation.Server.Host/Setup/PostSetupServices.cs +++ b/src/Tgstation.Server.Host/Setup/PostSetupServices.cs @@ -1,6 +1,8 @@ +using System; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; @@ -48,7 +50,7 @@ namespace Tgstation.Server.Host.Setup readonly IOptions fileLoggingConfigurationOptions; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The used to create . diff --git a/src/Tgstation.Server.Host/Setup/SetupApplication.cs b/src/Tgstation.Server.Host/Setup/SetupApplication.cs index b6a04bd536..fc45fb2ce6 100644 --- a/src/Tgstation.Server.Host/Setup/SetupApplication.cs +++ b/src/Tgstation.Server.Host/Setup/SetupApplication.cs @@ -1,8 +1,10 @@ +using System; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog.Events; -using System; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; @@ -28,12 +30,12 @@ namespace Tgstation.Server.Host.Setup protected static readonly IAssemblyInformationProvider AssemblyInformationProvider = new AssemblyInformationProvider(); /// - /// The for the + /// The for the . /// protected IConfiguration Configuration { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . public SetupApplication(IConfiguration configuration) diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs index 43ca5095b8..92b0f13d14 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs @@ -1,11 +1,4 @@ -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using MySql.Data.MySqlClient; -using Npgsql; -using System; +using System; using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; @@ -16,12 +9,22 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MySql.Data.MySqlClient; +using Npgsql; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Extensions.Converters; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; + using YamlDotNet.Serialization; namespace Tgstation.Server.Host.Setup @@ -30,37 +33,37 @@ namespace Tgstation.Server.Host.Setup sealed class SetupWizard : IHostedService { /// - /// The for the + /// The for the . /// readonly IIOManager ioManager; /// - /// The for the + /// The for the . /// readonly IConsole console; /// - /// The for the + /// The for the . /// readonly IHostEnvironment hostingEnvironment; /// - /// The for the + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the + /// The for the . /// readonly IDatabaseConnectionFactory dbConnectionFactory; /// - /// The for the + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; @@ -70,7 +73,7 @@ namespace Tgstation.Server.Host.Setup readonly IHostApplicationLifetime applicationLifetime; /// - /// The for the + /// The for the . /// readonly GeneralConfiguration generalConfiguration; @@ -80,18 +83,18 @@ namespace Tgstation.Server.Host.Setup TaskCompletionSource reloadTcs; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . /// The value of . /// The in use. - /// The containing the value of + /// The containing the value of . public SetupWizard( IIOManager ioManager, IConsole console, @@ -124,12 +127,22 @@ namespace Tgstation.Server.Host.Setup null); } + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + await CheckRunWizard(cancellationToken).ConfigureAwait(false); + applicationLifetime.StopApplication(); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + /// - /// A prompt for a yes or no value + /// A prompt for a yes or no value. /// - /// The question - /// The for the operation - /// A resulting in if the user replied yes, otherwise + /// The question . + /// The for the operation. + /// A resulting in if the user replied yes, otherwise. async Task PromptYesNo(string question, CancellationToken cancellationToken) { do @@ -147,10 +160,10 @@ namespace Tgstation.Server.Host.Setup } /// - /// Prompts the user to enter the port to host TGS on + /// Prompts the user to enter the port to host TGS on. /// - /// The for the operation - /// A resulting in the hosting port, or to use the default + /// The for the operation. + /// A resulting in the hosting port, or to use the default. async Task PromptForHostingPort(CancellationToken cancellationToken) { await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); @@ -264,6 +277,12 @@ namespace Tgstation.Server.Host.Setup ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(false); } + /// + /// Check that a given SQLite is can be accessed. Also prompts the user if they want to use a relative or absolute path. + /// + /// The path to the potential SQLite database file. + /// The for the operation. + /// A resulting in the SQLite database path to store in the configuration. async Task ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken) { var resolvedPath = ioManager.ResolvePath(databaseName); @@ -366,11 +385,11 @@ namespace Tgstation.Server.Host.Setup } /// - /// Prompts the user to create a + /// Prompts the user to create a . /// - /// The for the operation - /// A resulting in the new - #pragma warning disable CA1502 // TODO: Decomplexify + /// The for the operation. + /// A resulting in the new . +#pragma warning disable CA1502 // TODO: Decomplexify async Task ConfigureDatabase(CancellationToken cancellationToken) { bool firstTime = true; @@ -380,7 +399,7 @@ namespace Tgstation.Server.Host.Setup var databaseConfiguration = new DatabaseConfiguration { - DatabaseType = await PromptDatabaseType(firstTime, cancellationToken).ConfigureAwait(false) + DatabaseType = await PromptDatabaseType(firstTime, cancellationToken).ConfigureAwait(false), }; firstTime = false; @@ -484,7 +503,7 @@ namespace Tgstation.Server.Host.Setup var csb = new SqlConnectionStringBuilder { ApplicationName = assemblyInformationProvider.VersionPrefix, - DataSource = serverAddress ?? "(local)" + DataSource = serverAddress ?? "(local)", }; if (useWinAuth) @@ -509,7 +528,7 @@ namespace Tgstation.Server.Host.Setup { Server = serverAddress ?? "127.0.0.1", UserID = username, - Password = password + Password = password, }; if (serverPort.HasValue) @@ -526,7 +545,7 @@ namespace Tgstation.Server.Host.Setup var csb = new SqliteConnectionStringBuilder { DataSource = databaseName, - Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate + Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate, }; CreateTestConnection(csb.ConnectionString); @@ -541,7 +560,7 @@ namespace Tgstation.Server.Host.Setup ApplicationName = assemblyInformationProvider.VersionPrefix, Host = serverAddress ?? "127.0.0.1", Password = password, - Username = username + Username = username, }; if (serverPort.HasValue) @@ -576,18 +595,18 @@ namespace Tgstation.Server.Host.Setup } while (true); } - #pragma warning restore CA1502 +#pragma warning restore CA1502 /// - /// Prompts the user to create a + /// Prompts the user to create a . /// - /// The for the operation - /// A resulting in the new + /// The for the operation. + /// A resulting in the new . async Task ConfigureGeneral(CancellationToken cancellationToken) { var newGeneralConfiguration = new GeneralConfiguration { - SetupWizardMode = SetupWizardMode.Never + SetupWizardMode = SetupWizardMode.Never, }; do @@ -637,10 +656,10 @@ namespace Tgstation.Server.Host.Setup } /// - /// Prompts the user to create a + /// Prompts the user to create a . /// - /// The for the operation - /// A resulting in the new + /// The for the operation. + /// A resulting in the new . async Task ConfigureLogging(CancellationToken cancellationToken) { var fileLoggingConfiguration = new FileLoggingConfiguration(); @@ -722,16 +741,16 @@ namespace Tgstation.Server.Host.Setup } /// - /// Prompts the user to create a + /// Prompts the user to create a . /// - /// The for the operation - /// A resulting in the new + /// The for the operation. + /// A resulting in the new . async Task ConfigureControlPanel(CancellationToken cancellationToken) { var config = new ControlPanelConfiguration { Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(false), - AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false) + AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false), }; if (!config.AllowAnyOrigin) @@ -808,17 +827,17 @@ namespace Tgstation.Server.Host.Setup } /// - /// Saves a given set to + /// Saves a given set to . /// - /// The file to save the to - /// The hosting port to save - /// The to save - /// The to save - /// The to save - /// The to save + /// The file to save the to. + /// The hosting port to save. + /// The to save. + /// The to save. + /// The to save. + /// The to save. /// The to save. - /// The for the operation - /// A representing the running operation + /// The for the operation. + /// A representing the running operation. async Task SaveConfiguration( string userConfigFileName, ushort? hostingPort, @@ -893,11 +912,11 @@ namespace Tgstation.Server.Host.Setup } /// - /// Runs the + /// Runs the . /// - /// The path to the settings json to build - /// The for the operation - /// A representing the running operation + /// The path to the settings json to build. + /// The for the operation. + /// A representing the running operation. async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken) { // welcome message @@ -1007,15 +1026,5 @@ namespace Tgstation.Server.Host.Setup await finalTask.ConfigureAwait(false); } } - - /// - public async Task StartAsync(CancellationToken cancellationToken) - { - await CheckRunWizard(cancellationToken).ConfigureAwait(false); - applicationLifetime.StopApplication(); - } - - /// - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs b/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs index 2de9926e82..13107f734e 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Setup +namespace Tgstation.Server.Host.Setup { /// /// Determines if the will run. @@ -23,6 +23,6 @@ namespace Tgstation.Server.Host.Setup /// /// Never run the wizard. /// - Never + Never, } } diff --git a/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs b/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs index 3d0d5ae025..3ef76fc951 100644 --- a/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs +++ b/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Swarm diff --git a/src/Tgstation.Server.Host/Swarm/ISwarmService.cs b/src/Tgstation.Server.Host/Swarm/ISwarmService.cs index 84f2dc4b0c..6e6b7b942a 100644 --- a/src/Tgstation.Server.Host/Swarm/ISwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/ISwarmService.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Swarm diff --git a/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs b/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs index c5a5d4fbbf..49b45a65c3 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api; +using Tgstation.Server.Api; namespace Tgstation.Server.Host.Swarm { diff --git a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationRequest.cs b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationRequest.cs index 3de0403284..cad1cebe27 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationRequest.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationRequest.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Swarm diff --git a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs index 585c9b7e3a..87508af508 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Swarm +namespace Tgstation.Server.Host.Swarm { /// /// Result of attempting to register with a swarm controller. diff --git a/src/Tgstation.Server.Host/Swarm/SwarmServersUpdateRequest.cs b/src/Tgstation.Server.Host/Swarm/SwarmServersUpdateRequest.cs index c1e209549c..61b60cc024 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmServersUpdateRequest.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmServersUpdateRequest.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Swarm diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs index f737a09bf3..642431e666 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -1,8 +1,4 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -12,6 +8,12 @@ using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -53,15 +55,15 @@ namespace Tgstation.Server.Host.Swarm { ContractResolver = new DefaultContractResolver { - NamingStrategy = new CamelCaseNamingStrategy() + NamingStrategy = new CamelCaseNamingStrategy(), }, Converters = new JsonConverter[] { new VersionConverter(), - new BoolConverter() + new BoolConverter(), }, DefaultValueHandling = DefaultValueHandling.Ignore, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, }; /// @@ -185,7 +187,7 @@ namespace Tgstation.Server.Host.Swarm bool serversDirty; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -241,8 +243,8 @@ namespace Tgstation.Server.Host.Swarm { Address = swarmConfiguration.Address, Controller = swarmController, - Identifier = swarmConfiguration.Identifier - } + Identifier = swarmConfiguration.Identifier, + }, }; updateSynchronizationLock = new object(); @@ -450,149 +452,6 @@ namespace Tgstation.Server.Host.Swarm return PrepareUpdateImpl(version, false, cancellationToken); } - /// - /// Implementation of , - /// - /// The being updated to. - /// Whether or not the update request originated on this server. - /// The for the operation. - /// A resulting in whether or not the update should proceed. - async Task PrepareUpdateImpl(Version version, bool initiator, CancellationToken cancellationToken) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - - logger.LogTrace("PrepareUpdateImpl {0}...", version); - - if (!SwarmMode) - return true; - - if (version == targetUpdateVersion) - { - logger.LogDebug("Prepare update short circuit!"); - return true; - } - - using var httpClient = httpClientFactory.CreateClient(); - async Task RemotePrepareUpdate(SwarmServerResponse swarmServer) - { - using var request = PrepareSwarmRequest( - swarmServer, - HttpMethod.Put, - SwarmConstants.UpdateRoute, - new SwarmUpdateRequest - { - UpdateVersion = version - }); - - using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - return response.IsSuccessStatusCode; - } - - var shouldAbort = false; - try - { - lock (updateSynchronizationLock) - { - if (targetUpdateVersion == version) - { - logger.LogTrace("PrepareUpdateFromController early out, already prepared!"); - return true; - } - - if (targetUpdateVersion != null) - { - logger.LogDebug("Aborting update preparation, version {0} already prepared!", targetUpdateVersion); - shouldAbort = true; - return false; - } - - targetUpdateVersion = version; - } - - if (!swarmController && initiator) - { - logger.LogDebug("Forwarding update request to swarm controller..."); - var result = await RemotePrepareUpdate(null).ConfigureAwait(false); - if (result) - updateCommitTcs = new TaskCompletionSource(); - - return result; - } - - if (!initiator) - { - logger.LogTrace("Beginning local update process..."); - updateCommitTcs = new TaskCompletionSource(); - var updateApplyResult = await serverUpdater.BeginUpdate( - version, - cancellationToken) - .ConfigureAwait(false); - if (updateApplyResult != ServerUpdateResult.Started) - { - logger.LogWarning("Failed to prepare update! Result: {0}", updateApplyResult); - shouldAbort = true; - return false; - } - } - else - { - logger.LogTrace("No need to re-initiate update as it originated here on the swarm controller"); - updateCommitTcs = new TaskCompletionSource(); - } - - logger.LogDebug("Prepared for update to version {0}", version); - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to prepare update!"); - shouldAbort = true; - return false; - } - finally - { - if (shouldAbort) - await AbortUpdate(cancellationToken).ConfigureAwait(false); - } - - if (!swarmController) - return true; - - try - { - logger.LogTrace("Sending remote prepare to nodes..."); - List> tasks; - lock (swarmServers) - { - nodesThatNeedToBeReadyToCommit = new List( - swarmServers - .Where(x => !x.Controller) - .Select(x => x.Identifier)); - tasks = swarmServers - .Where(x => !x.Controller) - .Select(RemotePrepareUpdate) - .ToList(); - } - - await Task.WhenAll(tasks); - - // if all succeeds... - if (tasks.All(x => x.Result)) - { - logger.LogDebug("Distributed prepare for update to version {0} complete.", version); - return true; - } - } - catch (Exception ex) - { - logger.LogWarning(ex, "Error remotely preparing updates!"); - } - - logger.LogDebug("Distrubuted prepare failed!"); - await AbortUpdate(cancellationToken).ConfigureAwait(false); - return false; - } - /// public async Task Initialize(CancellationToken cancellationToken) { @@ -606,7 +465,7 @@ namespace Tgstation.Server.Host.Swarm else logger.LogTrace("Swarm mode disabled"); - var _ = lazyRestartRegistration.Value; + _ = lazyRestartRegistration.Value; SwarmRegistrationResult result; if (swarmController) @@ -706,315 +565,6 @@ namespace Tgstation.Server.Host.Swarm } } - /// - /// Ping each node to see that they are still running. - /// - /// The for the operation. - /// A representing the running operation. - async Task HealthCheckNodes(CancellationToken cancellationToken) - { - using var httpClient = httpClientFactory.CreateClient(); - - List currentSwarmServers; - lock (swarmServers) - currentSwarmServers = swarmServers.ToList(); - - async Task HealthRequestForServer(SwarmServerResponse swarmServer) - { - using var request = PrepareSwarmRequest( - swarmServer, - HttpMethod.Get, - String.Empty, - null); - - try - { - using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - return; - } - catch (Exception ex) when (!(ex is OperationCanceledException)) - { - logger.LogWarning( - ex, - "Error during swarm server health check on node '{0}'! Unregistering...", - swarmServer.Identifier); - } - - lock (swarmServers) - { - swarmServers.Remove(swarmServer); - registrationIds.Remove(swarmServer.Identifier); - }; - } - - await Task.WhenAll( - currentSwarmServers - .Where(x => !x.Controller) - .Select(HealthRequestForServer)) - .ConfigureAwait(false); - - lock (swarmServers) - if (swarmServers.Count != currentSwarmServers.Count) - MarkServersDirty(); - - if (serversDirty) - await SendUpdatedServerListToNodes(cancellationToken).ConfigureAwait(false); - } - - /// - /// Set and complete the current . - /// - void MarkServersDirty() - { - serversDirty = true; - if(TriggerHealthCheck()) - logger.LogTrace("Server list is dirty!"); - } - - /// - /// Complete the current . - /// - /// the result of the call to . - bool TriggerHealthCheck() - { - var currentTcs = forceHealthCheckTcs; - forceHealthCheckTcs = new TaskCompletionSource(); - return currentTcs.TrySetResult(null); - } - - /// - /// Ping the swarm controller to see that it is still running. If need be, reregister. - /// - /// The for the operation. - /// A representing the running operation. - async Task HealthCheckController(CancellationToken cancellationToken) - { - using var httpClient = httpClientFactory.CreateClient(); - - if (controllerRegistration.HasValue) - try - { - using var request = PrepareSwarmRequest( - null, - HttpMethod.Get, - String.Empty, - null); - using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - logger.LogTrace("Controller health check successful"); - return; - } - catch (Exception ex) - { - logger.LogWarning(ex, "Error during swarm controller health check! Attempting to re-register..."); - controllerRegistration = null; - } - - SwarmRegistrationResult registrationResult; - for (var I = 1UL; ; ++I) - { - logger.LogInformation("Swarm re-registration attempt {0}...", I); - registrationResult = await RegisterWithController(cancellationToken).ConfigureAwait(false); - - if (registrationResult == SwarmRegistrationResult.Success) - return; - - if (registrationResult == SwarmRegistrationResult.Unauthorized) - { - logger.LogError("Swarm re-registration failed, controller's private key has changed!"); - break; - } - - if (registrationResult == SwarmRegistrationResult.VersionMismatch) - { - logger.LogError("Swarm re-registration failed, controller's TGS version has changed!"); - break; - } - - await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken); - } - - // we could do something here... but what? - // best to just let the health check loop keep retrying... we won't be able to update at least - } - - /// - /// Attempt to register the node with the controller. - /// - /// The for the operation. - /// A resulting in the . - async Task RegisterWithController(CancellationToken cancellationToken) - { - logger.LogInformation("Attempting to register with swarm controller at {0}...", swarmConfiguration.ControllerAddress); - var requestedRegistrationId = Guid.NewGuid(); - - using var httpClient = httpClientFactory.CreateClient(); - using var registrationRequest = PrepareSwarmRequest( - null, - HttpMethod.Post, - SwarmConstants.RegisterRoute, - new SwarmRegistrationRequest - { - ServerVersion = assemblyInformationProvider.Version, - Identifier = swarmConfiguration.Identifier, - Address = swarmConfiguration.Address - }, - requestedRegistrationId); - - try - { - using var response = await httpClient.SendAsync(registrationRequest, cancellationToken).ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - logger.LogInformation("Sucessfully registered with ID {0}", requestedRegistrationId); - controllerRegistration = requestedRegistrationId; - lastControllerHealthCheck = DateTimeOffset.UtcNow; - return SwarmRegistrationResult.Success; - } - - logger.LogWarning("Unable to register with swarm: HTTP {0}!", response.StatusCode); - - if (response.StatusCode == HttpStatusCode.Unauthorized) - return SwarmRegistrationResult.Unauthorized; - - if (response.StatusCode == HttpStatusCode.UpgradeRequired) - return SwarmRegistrationResult.VersionMismatch; - - logger.LogWarning("Error registering with swarm controller: HTTP {0}", response.StatusCode); - try - { - var responseData = await response.Content.ReadAsStringAsync(); - if (!String.IsNullOrWhiteSpace(responseData)) - logger.LogDebug("Response:{0}{1}", Environment.NewLine, responseData); - } - catch (Exception ex) - { - logger.LogDebug(ex, "Error reading registration response content stream!"); - } - } - catch (Exception ex) - { - logger.LogWarning(ex, "Error sending registration request!"); - } - - return SwarmRegistrationResult.CommunicationFailure; - } - - /// - /// Sends the controllers list of nodes to all nodes. - /// - /// The for the operation. - /// A representing the running operation. - async Task SendUpdatedServerListToNodes(CancellationToken cancellationToken) - { - logger.LogDebug("Sending updated server list to all nodes..."); - List currentSwarmServers; - lock (swarmServers) - currentSwarmServers = swarmServers.ToList(); - - using var httpClient = httpClientFactory.CreateClient(); - async Task UpdateRequestForServer(SwarmServerResponse swarmServer) - { - using var request = PrepareSwarmRequest( - swarmServer, - HttpMethod.Post, - String.Empty, - new SwarmServersUpdateRequest - { - SwarmServers = currentSwarmServers - }); - - try - { - using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - } - catch (Exception ex) when (!(ex is OperationCanceledException)) - { - logger.LogWarning(ex, "Error during swarm server list update for node '{0}'! Unregistering...", swarmServer.Identifier); - - lock (swarmServers) - { - swarmServers.Remove(swarmServer); - registrationIds.Remove(swarmServer.Identifier); - } - } - } - - await Task.WhenAll( - currentSwarmServers - .Where(x => !x.Controller) - .Select(UpdateRequestForServer)) - .ConfigureAwait(false); - serversDirty = false; - } - - /// - /// Prepares a for swarm communication. - /// - /// The the message is for, if null will be sent to swarm controller. - /// The . - /// The route on to use. - /// The body if any. - /// An optional override to the . - /// A new . - HttpRequestMessage PrepareSwarmRequest( - SwarmServerResponse swarmServer, - HttpMethod httpMethod, - string subroute, - object body, - Guid? registrationIdOverride = null) - { - swarmServer ??= new SwarmServerResponse - { - Address = swarmConfiguration.ControllerAddress, - }; - - subroute = $"{SwarmConstants.ControllerRoute}/{subroute}"; - logger.LogTrace( - "{0} {1} to swarm server {2}", - httpMethod, - subroute, - swarmServer.Identifier ?? swarmServer.Address.ToString()); - - var request = new HttpRequestMessage( - httpMethod, - swarmServer.Address + subroute[1..]); - - request.Headers.Add(SwarmConstants.ApiKeyHeader, swarmConfiguration.PrivateKey); - request.Headers.UserAgent.Clear(); - request.Headers.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); - if (registrationIdOverride.HasValue) - request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdOverride.Value.ToString()); - else if (swarmController) - { - lock (swarmServers) - if (registrationIds.TryGetValue(swarmServer.Identifier, out var registrationId)) - request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationId.ToString()); - } - else if (controllerRegistration.HasValue) - request.Headers.Add(SwarmConstants.RegistrationIdHeader, controllerRegistration.Value.ToString()); - - try - { - if (body != null) - request.Content = new StringContent( - JsonConvert.SerializeObject(body, SerializerSettings), - Encoding.UTF8, - MediaTypeNames.Application.Json); - - return request; - } - catch - { - request.Dispose(); - throw; - } - } - /// public void UpdateSwarmServersList(IEnumerable swarmServers) { @@ -1032,89 +582,6 @@ namespace Tgstation.Server.Host.Swarm } } - /// - /// Timed loop for calling . - /// - /// The for the operation - /// A representing the rinning operation - async Task HealthCheckLoop(CancellationToken cancellationToken) - { - logger.LogTrace("Starting HealthCheckLoop..."); - try - { - var nextForceHealthCheckTask = forceHealthCheckTcs.Task; - while (!cancellationToken.IsCancellationRequested) - { - TimeSpan delay; - if (swarmController) - delay = TimeSpan.FromMinutes(ControllerHealthCheckIntervalMinutes); - else - { - delay = TimeSpan.FromMinutes(NodeHealthCheckIntervalMinutes); - if (lastControllerHealthCheck.HasValue) - { - var recommendedTimeOfNextCheck = lastControllerHealthCheck.Value + delay; - - if (recommendedTimeOfNextCheck > DateTimeOffset.UtcNow) - delay = recommendedTimeOfNextCheck - DateTimeOffset.UtcNow; - } - } - - var delayTask = asyncDelayer.Delay( - delay, - cancellationToken); - - var awakeningTask = Task.WhenAny( - delayTask, - nextForceHealthCheckTask); - - await awakeningTask.ConfigureAwait(false); - - if (nextForceHealthCheckTask.IsCompleted && swarmController) - { - // Intentionally wait a few seconds for the other server to start up before interogating it - logger.LogTrace("Next health check triggering in {0}s...", SecondsToDelayForcedHealthChecks); - await asyncDelayer.Delay(TimeSpan.FromSeconds(SecondsToDelayForcedHealthChecks), cancellationToken); - } - else if (!swarmController && !nextForceHealthCheckTask.IsCompleted) - { - if (!lastControllerHealthCheck.HasValue) - { - logger.LogTrace("Not initially registered with controller, skipping health check."); - continue; // unregistered - } - - if ((DateTimeOffset.UtcNow - lastControllerHealthCheck.Value).TotalMinutes < NodeHealthCheckIntervalMinutes) - { - logger.LogTrace("Controller seems to be active, skipping health check."); - continue; - } - } - - nextForceHealthCheckTask = forceHealthCheckTcs.Task; - - logger.LogTrace("Performing swarm health check..."); - try - { - if (swarmController) - await HealthCheckNodes(cancellationToken).ConfigureAwait(false); - else - await HealthCheckController(cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when (!(ex is OperationCanceledException)) - { - logger.LogError(ex, "Health check error!"); - } - } - } - catch (OperationCanceledException ex) - { - logger.LogTrace(ex, "Health check loop cancelled!"); - } - - logger.LogTrace("Stopped HealthCheckLoop"); - } - /// public bool ValidateRegistration(Guid registrationId) { @@ -1243,29 +710,6 @@ namespace Tgstation.Server.Host.Swarm return true; } - /// - /// Gets the from a given . - /// - /// The registration . - /// The registered or if it does not exist. - string NodeIdentifierFromRegistration(Guid registrationId) - { - if (!swarmController) - throw new InvalidOperationException("NodeIdentifierFromRegistration on node!"); - - lock (swarmServers) - { - var exists = registrationIds.Any(x => x.Value == registrationId); - if (!exists) - { - logger.LogDebug("A node that was to be looked up ({0}) disappeared from our records!", registrationId); - return null; - } - - return registrationIds.First(x => x.Value == registrationId).Key; - } - } - /// public async Task UnregisterNode(Guid registrationId, CancellationToken cancellationToken) { @@ -1293,5 +737,563 @@ namespace Tgstation.Server.Host.Swarm MarkServersDirty(); } + + /// + /// Implementation of ,. + /// + /// The being updated to. + /// Whether or not the update request originated on this server. + /// The for the operation. + /// A resulting in whether or not the update should proceed. + async Task PrepareUpdateImpl(Version version, bool initiator, CancellationToken cancellationToken) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + + logger.LogTrace("PrepareUpdateImpl {0}...", version); + + if (!SwarmMode) + return true; + + if (version == targetUpdateVersion) + { + logger.LogDebug("Prepare update short circuit!"); + return true; + } + + using var httpClient = httpClientFactory.CreateClient(); + async Task RemotePrepareUpdate(SwarmServerResponse swarmServer) + { + using var request = PrepareSwarmRequest( + swarmServer, + HttpMethod.Put, + SwarmConstants.UpdateRoute, + new SwarmUpdateRequest + { + UpdateVersion = version, + }); + + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + return response.IsSuccessStatusCode; + } + + var shouldAbort = false; + try + { + lock (updateSynchronizationLock) + { + if (targetUpdateVersion == version) + { + logger.LogTrace("PrepareUpdateFromController early out, already prepared!"); + return true; + } + + if (targetUpdateVersion != null) + { + logger.LogDebug("Aborting update preparation, version {0} already prepared!", targetUpdateVersion); + shouldAbort = true; + return false; + } + + targetUpdateVersion = version; + } + + if (!swarmController && initiator) + { + logger.LogDebug("Forwarding update request to swarm controller..."); + var result = await RemotePrepareUpdate(null).ConfigureAwait(false); + if (result) + updateCommitTcs = new TaskCompletionSource(); + + return result; + } + + if (!initiator) + { + logger.LogTrace("Beginning local update process..."); + updateCommitTcs = new TaskCompletionSource(); + var updateApplyResult = await serverUpdater.BeginUpdate( + version, + cancellationToken) + .ConfigureAwait(false); + if (updateApplyResult != ServerUpdateResult.Started) + { + logger.LogWarning("Failed to prepare update! Result: {0}", updateApplyResult); + shouldAbort = true; + return false; + } + } + else + { + logger.LogTrace("No need to re-initiate update as it originated here on the swarm controller"); + updateCommitTcs = new TaskCompletionSource(); + } + + logger.LogDebug("Prepared for update to version {0}", version); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to prepare update!"); + shouldAbort = true; + return false; + } + finally + { + if (shouldAbort) + await AbortUpdate(cancellationToken).ConfigureAwait(false); + } + + if (!swarmController) + return true; + + try + { + logger.LogTrace("Sending remote prepare to nodes..."); + List> tasks; + lock (swarmServers) + { + nodesThatNeedToBeReadyToCommit = new List( + swarmServers + .Where(x => !x.Controller) + .Select(x => x.Identifier)); + tasks = swarmServers + .Where(x => !x.Controller) + .Select(RemotePrepareUpdate) + .ToList(); + } + + await Task.WhenAll(tasks); + + // if all succeeds... + if (tasks.All(x => x.Result)) + { + logger.LogDebug("Distributed prepare for update to version {0} complete.", version); + return true; + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "Error remotely preparing updates!"); + } + + logger.LogDebug("Distrubuted prepare failed!"); + await AbortUpdate(cancellationToken).ConfigureAwait(false); + return false; + } + + /// + /// Ping each node to see that they are still running. + /// + /// The for the operation. + /// A representing the running operation. + async Task HealthCheckNodes(CancellationToken cancellationToken) + { + using var httpClient = httpClientFactory.CreateClient(); + + List currentSwarmServers; + lock (swarmServers) + currentSwarmServers = swarmServers.ToList(); + + async Task HealthRequestForServer(SwarmServerResponse swarmServer) + { + using var request = PrepareSwarmRequest( + swarmServer, + HttpMethod.Get, + String.Empty, + null); + + try + { + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + return; + } + catch (Exception ex) when (!(ex is OperationCanceledException)) + { + logger.LogWarning( + ex, + "Error during swarm server health check on node '{0}'! Unregistering...", + swarmServer.Identifier); + } + + lock (swarmServers) + { + swarmServers.Remove(swarmServer); + registrationIds.Remove(swarmServer.Identifier); + } + } + + await Task.WhenAll( + currentSwarmServers + .Where(x => !x.Controller) + .Select(HealthRequestForServer)) + .ConfigureAwait(false); + + lock (swarmServers) + if (swarmServers.Count != currentSwarmServers.Count) + MarkServersDirty(); + + if (serversDirty) + await SendUpdatedServerListToNodes(cancellationToken).ConfigureAwait(false); + } + + /// + /// Set and complete the current . + /// + void MarkServersDirty() + { + serversDirty = true; + if (TriggerHealthCheck()) + logger.LogTrace("Server list is dirty!"); + } + + /// + /// Complete the current . + /// + /// the result of the call to . + bool TriggerHealthCheck() + { + var currentTcs = forceHealthCheckTcs; + forceHealthCheckTcs = new TaskCompletionSource(); + return currentTcs.TrySetResult(null); + } + + /// + /// Ping the swarm controller to see that it is still running. If need be, reregister. + /// + /// The for the operation. + /// A representing the running operation. + async Task HealthCheckController(CancellationToken cancellationToken) + { + using var httpClient = httpClientFactory.CreateClient(); + + if (controllerRegistration.HasValue) + try + { + using var request = PrepareSwarmRequest( + null, + HttpMethod.Get, + String.Empty, + null); + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + logger.LogTrace("Controller health check successful"); + return; + } + catch (Exception ex) + { + logger.LogWarning(ex, "Error during swarm controller health check! Attempting to re-register..."); + controllerRegistration = null; + } + + SwarmRegistrationResult registrationResult; + for (var registrationAttempt = 1UL; ; ++registrationAttempt) + { + logger.LogInformation("Swarm re-registration attempt {0}...", registrationAttempt); + registrationResult = await RegisterWithController(cancellationToken).ConfigureAwait(false); + + if (registrationResult == SwarmRegistrationResult.Success) + return; + + if (registrationResult == SwarmRegistrationResult.Unauthorized) + { + logger.LogError("Swarm re-registration failed, controller's private key has changed!"); + break; + } + + if (registrationResult == SwarmRegistrationResult.VersionMismatch) + { + logger.LogError("Swarm re-registration failed, controller's TGS version has changed!"); + break; + } + + await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken); + } + + // we could do something here... but what? + // best to just let the health check loop keep retrying... we won't be able to update at least + } + + /// + /// Attempt to register the node with the controller. + /// + /// The for the operation. + /// A resulting in the . + async Task RegisterWithController(CancellationToken cancellationToken) + { + logger.LogInformation("Attempting to register with swarm controller at {0}...", swarmConfiguration.ControllerAddress); + var requestedRegistrationId = Guid.NewGuid(); + + using var httpClient = httpClientFactory.CreateClient(); + using var registrationRequest = PrepareSwarmRequest( + null, + HttpMethod.Post, + SwarmConstants.RegisterRoute, + new SwarmRegistrationRequest + { + ServerVersion = assemblyInformationProvider.Version, + Identifier = swarmConfiguration.Identifier, + Address = swarmConfiguration.Address, + }, + requestedRegistrationId); + + try + { + using var response = await httpClient.SendAsync(registrationRequest, cancellationToken).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + logger.LogInformation("Sucessfully registered with ID {0}", requestedRegistrationId); + controllerRegistration = requestedRegistrationId; + lastControllerHealthCheck = DateTimeOffset.UtcNow; + return SwarmRegistrationResult.Success; + } + + logger.LogWarning("Unable to register with swarm: HTTP {0}!", response.StatusCode); + + if (response.StatusCode == HttpStatusCode.Unauthorized) + return SwarmRegistrationResult.Unauthorized; + + if (response.StatusCode == HttpStatusCode.UpgradeRequired) + return SwarmRegistrationResult.VersionMismatch; + + logger.LogWarning("Error registering with swarm controller: HTTP {0}", response.StatusCode); + try + { + var responseData = await response.Content.ReadAsStringAsync(); + if (!String.IsNullOrWhiteSpace(responseData)) + logger.LogDebug("Response:{0}{1}", Environment.NewLine, responseData); + } + catch (Exception ex) + { + logger.LogDebug(ex, "Error reading registration response content stream!"); + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "Error sending registration request!"); + } + + return SwarmRegistrationResult.CommunicationFailure; + } + + /// + /// Sends the controllers list of nodes to all nodes. + /// + /// The for the operation. + /// A representing the running operation. + async Task SendUpdatedServerListToNodes(CancellationToken cancellationToken) + { + logger.LogDebug("Sending updated server list to all nodes..."); + List currentSwarmServers; + lock (swarmServers) + currentSwarmServers = swarmServers.ToList(); + + using var httpClient = httpClientFactory.CreateClient(); + async Task UpdateRequestForServer(SwarmServerResponse swarmServer) + { + using var request = PrepareSwarmRequest( + swarmServer, + HttpMethod.Post, + String.Empty, + new SwarmServersUpdateRequest + { + SwarmServers = currentSwarmServers, + }); + + try + { + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + catch (Exception ex) when (!(ex is OperationCanceledException)) + { + logger.LogWarning(ex, "Error during swarm server list update for node '{0}'! Unregistering...", swarmServer.Identifier); + + lock (swarmServers) + { + swarmServers.Remove(swarmServer); + registrationIds.Remove(swarmServer.Identifier); + } + } + } + + await Task.WhenAll( + currentSwarmServers + .Where(x => !x.Controller) + .Select(UpdateRequestForServer)) + .ConfigureAwait(false); + serversDirty = false; + } + + /// + /// Prepares a for swarm communication. + /// + /// The the message is for, if null will be sent to swarm controller. + /// The . + /// The route on to use. + /// The body if any. + /// An optional override to the . + /// A new . + HttpRequestMessage PrepareSwarmRequest( + SwarmServerResponse swarmServer, + HttpMethod httpMethod, + string subroute, + object body, + Guid? registrationIdOverride = null) + { + swarmServer ??= new SwarmServerResponse + { + Address = swarmConfiguration.ControllerAddress, + }; + + subroute = $"{SwarmConstants.ControllerRoute}/{subroute}"; + logger.LogTrace( + "{0} {1} to swarm server {2}", + httpMethod, + subroute, + swarmServer.Identifier ?? swarmServer.Address.ToString()); + + var request = new HttpRequestMessage( + httpMethod, + swarmServer.Address + subroute[1..]); + + request.Headers.Add(SwarmConstants.ApiKeyHeader, swarmConfiguration.PrivateKey); + request.Headers.UserAgent.Clear(); + request.Headers.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + if (registrationIdOverride.HasValue) + request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdOverride.Value.ToString()); + else if (swarmController) + { + lock (swarmServers) + if (registrationIds.TryGetValue(swarmServer.Identifier, out var registrationId)) + request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationId.ToString()); + } + else if (controllerRegistration.HasValue) + request.Headers.Add(SwarmConstants.RegistrationIdHeader, controllerRegistration.Value.ToString()); + + try + { + if (body != null) + request.Content = new StringContent( + JsonConvert.SerializeObject(body, SerializerSettings), + Encoding.UTF8, + MediaTypeNames.Application.Json); + + return request; + } + catch + { + request.Dispose(); + throw; + } + } + + /// + /// Timed loop for calling . + /// + /// The for the operation. + /// A representing the rinning operation. + async Task HealthCheckLoop(CancellationToken cancellationToken) + { + logger.LogTrace("Starting HealthCheckLoop..."); + try + { + var nextForceHealthCheckTask = forceHealthCheckTcs.Task; + while (!cancellationToken.IsCancellationRequested) + { + TimeSpan delay; + if (swarmController) + delay = TimeSpan.FromMinutes(ControllerHealthCheckIntervalMinutes); + else + { + delay = TimeSpan.FromMinutes(NodeHealthCheckIntervalMinutes); + if (lastControllerHealthCheck.HasValue) + { + var recommendedTimeOfNextCheck = lastControllerHealthCheck.Value + delay; + + if (recommendedTimeOfNextCheck > DateTimeOffset.UtcNow) + delay = recommendedTimeOfNextCheck - DateTimeOffset.UtcNow; + } + } + + var delayTask = asyncDelayer.Delay( + delay, + cancellationToken); + + var awakeningTask = Task.WhenAny( + delayTask, + nextForceHealthCheckTask); + + await awakeningTask.ConfigureAwait(false); + + if (nextForceHealthCheckTask.IsCompleted && swarmController) + { + // Intentionally wait a few seconds for the other server to start up before interogating it + logger.LogTrace("Next health check triggering in {0}s...", SecondsToDelayForcedHealthChecks); + await asyncDelayer.Delay(TimeSpan.FromSeconds(SecondsToDelayForcedHealthChecks), cancellationToken); + } + else if (!swarmController && !nextForceHealthCheckTask.IsCompleted) + { + if (!lastControllerHealthCheck.HasValue) + { + logger.LogTrace("Not initially registered with controller, skipping health check."); + continue; // unregistered + } + + if ((DateTimeOffset.UtcNow - lastControllerHealthCheck.Value).TotalMinutes < NodeHealthCheckIntervalMinutes) + { + logger.LogTrace("Controller seems to be active, skipping health check."); + continue; + } + } + + nextForceHealthCheckTask = forceHealthCheckTcs.Task; + + logger.LogTrace("Performing swarm health check..."); + try + { + if (swarmController) + await HealthCheckNodes(cancellationToken).ConfigureAwait(false); + else + await HealthCheckController(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (!(ex is OperationCanceledException)) + { + logger.LogError(ex, "Health check error!"); + } + } + } + catch (OperationCanceledException ex) + { + logger.LogTrace(ex, "Health check loop cancelled!"); + } + + logger.LogTrace("Stopped HealthCheckLoop"); + } + + /// + /// Gets the from a given . + /// + /// The registration . + /// The registered or if it does not exist. + string NodeIdentifierFromRegistration(Guid registrationId) + { + if (!swarmController) + throw new InvalidOperationException("NodeIdentifierFromRegistration on node!"); + + lock (swarmServers) + { + var exists = registrationIds.Any(x => x.Value == registrationId); + if (!exists) + { + logger.LogDebug("A node that was to be looked up ({0}) disappeared from our records!", registrationId); + return null; + } + + return registrationIds.First(x => x.Value == registrationId).Key; + } + } } } diff --git a/src/Tgstation.Server.Host/Swarm/SwarmUpdateRequest.cs b/src/Tgstation.Server.Host/Swarm/SwarmUpdateRequest.cs index 410794de68..c3b0ef0de3 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmUpdateRequest.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmUpdateRequest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Swarm diff --git a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs index b4933d7b6c..115ad60ba8 100644 --- a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Net.Http.Headers; using System.Reflection; + using Tgstation.Server.Api; namespace Tgstation.Server.Host.System @@ -29,7 +30,7 @@ namespace Tgstation.Server.Host.System Version.ToString()); /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// public AssemblyInformationProvider() { diff --git a/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs index 162b627888..184dd9f60a 100644 --- a/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http.Headers; using System.Reflection; diff --git a/src/Tgstation.Server.Host/System/INetworkPromptReaper.cs b/src/Tgstation.Server.Host/System/INetworkPromptReaper.cs index 7202932df5..0c25777127 100644 --- a/src/Tgstation.Server.Host/System/INetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/System/INetworkPromptReaper.cs @@ -1,14 +1,14 @@ namespace Tgstation.Server.Host.System { /// - /// On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort(). This looks out for those prompts and immediately clicks "Yes" if the owning process has registered for it + /// On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort(). This looks out for those prompts and immediately clicks "Yes" if the owning process has registered for it. /// interface INetworkPromptReaper { /// - /// Register a given for network prompt reaping + /// Register a given for network prompt reaping. /// - /// The to register + /// The to register. void RegisterProcess(IProcess process); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs b/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs index 2036796994..26038d7887 100644 --- a/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs +++ b/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs @@ -1,17 +1,17 @@ namespace Tgstation.Server.Host.System { /// - /// For identifying the current platform + /// For identifying the current platform. /// public interface IPlatformIdentifier { /// - /// If the current platform is a Windows platform + /// If the current platform is a Windows platform. /// bool IsWindows { get; } /// - /// The extension of executable script files for the system + /// The extension of executable script files for the system. /// string ScriptFileExtension { get; } } diff --git a/src/Tgstation.Server.Host/System/IProcess.cs b/src/Tgstation.Server.Host/System/IProcess.cs index 8392d58377..7dfaf923b2 100644 --- a/src/Tgstation.Server.Host/System/IProcess.cs +++ b/src/Tgstation.Server.Host/System/IProcess.cs @@ -1,43 +1,43 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.System { /// - /// Abstraction over a + /// Abstraction over a . /// interface IProcess : IProcessBase, IDisposable { /// - /// The ' ID + /// The ' ID. /// int Id { get; } /// - /// The representing the time until the becomes "idle" + /// The representing the time until the becomes "idle". /// Task Startup { get; } /// - /// Get the stderr output of the + /// Get the stderr output of the . /// /// The for the operation. - /// A resulting in the stderr output of the + /// A resulting in the stderr output of the . Task GetErrorOutput(CancellationToken cancellationToken); /// - /// Get the stdout output of the + /// Get the stdout output of the . /// /// The for the operation. - /// A resulting in the stdout output of the + /// A resulting in the stdout output of the . Task GetStandardOutput(CancellationToken cancellationToken); /// - /// Get the stderr and stdout output of the + /// Get the stderr and stdout output of the . /// /// The for the operation. - /// A resulting in the stderr and stdout output of the + /// A resulting in the stderr and stdout output of the . Task GetCombinedOutput(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index 3530b619b8..a38df065d3 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -4,17 +4,17 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.System { /// - /// Represents process lifetime + /// Represents process lifetime. /// interface IProcessBase { /// - /// The resulting in the exit code of the process + /// The resulting in the exit code of the process. /// Task Lifetime { get; } /// - /// Set's the owned to + /// Set's the owned to . /// void SetHighPriority(); diff --git a/src/Tgstation.Server.Host/System/IProcessExecutor.cs b/src/Tgstation.Server.Host/System/IProcessExecutor.cs index 12bfb896a9..0ab2e90f02 100644 --- a/src/Tgstation.Server.Host/System/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/IProcessExecutor.cs @@ -1,20 +1,20 @@ -namespace Tgstation.Server.Host.System +namespace Tgstation.Server.Host.System { /// - /// For launching ' + /// For launching '. /// interface IProcessExecutor { /// - /// Launch a + /// Launch a . /// - /// The full path to the executable file - /// The working directory for the - /// The arguments for the - /// If standard output should be read - /// If standard error should be read + /// The full path to the executable file. + /// The working directory for the . + /// The arguments for the . + /// If standard output should be read. + /// If standard error should be read. /// If shell execute should not be used. Must be set if or are set. - /// A new + /// A new . IProcess LaunchProcess( string fileName, string workingDirectory, diff --git a/src/Tgstation.Server.Host/System/IProcessFeatures.cs b/src/Tgstation.Server.Host/System/IProcessFeatures.cs index 2ba9503f6a..b45d0831e0 100644 --- a/src/Tgstation.Server.Host/System/IProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/IProcessFeatures.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.System diff --git a/src/Tgstation.Server.Host/System/PlatformIdentifier.cs b/src/Tgstation.Server.Host/System/PlatformIdentifier.cs index c2ec1382aa..38101248f6 100644 --- a/src/Tgstation.Server.Host/System/PlatformIdentifier.cs +++ b/src/Tgstation.Server.Host/System/PlatformIdentifier.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.System public string ScriptFileExtension { get; } /// - /// Construct a + /// Initializes a new instance of the class. /// public PlatformIdentifier() { diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 62ba10172a..012e4b2291 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -1,9 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.Extensions.Logging; using Mono.Unix; using Mono.Unix.Native; -using System; -using System.Threading; -using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; @@ -29,7 +31,7 @@ namespace Tgstation.Server.Host.System readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index e034e1aedc..a3bc854915 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Win32.SafeHandles; -using System; +using System; using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Win32.SafeHandles; + using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; @@ -28,29 +30,47 @@ namespace Tgstation.Server.Host.System readonly IProcessFeatures processFeatures; /// - /// The for the + /// The for the . /// readonly ILogger logger; + /// + /// The . + /// readonly global::System.Diagnostics.Process handle; + /// + /// The . + /// + /// We keep this to prevent .NET from closing the real handle too soon. See https://stackoverflow.com/a/47656845 readonly SafeProcessHandle safeHandle; + /// + /// The resulting in the process' standard output text. + /// readonly Task standardOutputTask; + + /// + /// The resulting in the process' standard error text. + /// readonly Task standardErrorTask; + + /// + /// The resulting in the process' unified standard output and standard error text. + /// readonly StringBuilder combinedStringBuilder; /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// If was NOT just created + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// If was NOT just created. public Process( IProcessFeatures processFeatures, global::System.Diagnostics.Process handle, @@ -64,10 +84,8 @@ namespace Tgstation.Server.Host.System this.handle = handle ?? throw new ArgumentNullException(nameof(handle)); // Do this fast because the runtime will bitch if we try to access it after it ends - Id = handle.Id; - - // https://stackoverflow.com/a/47656845 safeHandle = handle.SafeHandle; + Id = handle.Id; this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); @@ -111,13 +129,6 @@ namespace Tgstation.Server.Host.System handle.Dispose(); } - async Task WrapLifetimeTask(Task lifetimeTask) - { - var exitCode = await lifetimeTask.ConfigureAwait(false); - logger.LogTrace("PID {0} exited with code {1}", Id, exitCode); - return exitCode; - } - /// public async Task GetCombinedOutput(CancellationToken cancellationToken) { @@ -226,5 +237,17 @@ namespace Tgstation.Server.Host.System logger.LogTrace("Dumping PID {0} to {1}...", Id, outputFile); return processFeatures.CreateDump(handle, outputFile, cancellationToken); } + + /// + /// Attaches a log message to the process' exit event. + /// + /// The original lifetime . + /// A functionally identical to . + async Task WrapLifetimeTask(Task lifetimeTask) + { + var exitCode = await lifetimeTask.ConfigureAwait(false); + logger.LogTrace("PID {0} exited with code {1}", Id, exitCode); + return exitCode; + } } } diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 454542ca18..907d056e26 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -1,9 +1,10 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.IO; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + namespace Tgstation.Server.Host.System { /// @@ -15,15 +16,21 @@ namespace Tgstation.Server.Host.System readonly IProcessFeatures processFeatures; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly ILoggerFactory loggerFactory; + /// + /// Wrapper for to safely provide the process ID. + /// + /// The to attach an exit handler to. + /// A that completes once the process represented by launches. + /// The result of the call to . async Task> AttachExitHandlerBeforeLaunch(global::System.Diagnostics.Process handle, Task startupTask) { var id = -1; @@ -33,6 +40,12 @@ namespace Tgstation.Server.Host.System return result; } + /// + /// Attach an asychronous exit handler to a given process . + /// + /// The to attach an exit handler to. + /// A that can be called to get the safely. + /// A that completes with the exit code of the process represented by . Task AttachExitHandler(global::System.Diagnostics.Process handle, Func idProvider) { handle.EnableRaisingEvents = true; @@ -79,11 +92,11 @@ namespace Tgstation.Server.Host.System } /// - /// Construct a + /// Initializes a new instance of the class. /// /// The value of . - /// The value of - /// The value of + /// The value of . + /// The value of . public ProcessExecutor( IProcessFeatures processFeatures, ILogger logger, @@ -216,7 +229,8 @@ namespace Tgstation.Server.Host.System outputTask, errorTask, combinedStringBuilder, - loggerFactory.CreateLogger(), false); + loggerFactory.CreateLogger(), + false); return process; } diff --git a/src/Tgstation.Server.Host/System/ProgramShutdownTokenSource.cs b/src/Tgstation.Server.Host/System/ProgramShutdownTokenSource.cs index 3ca60dbab9..fb68be4ac9 100644 --- a/src/Tgstation.Server.Host/System/ProgramShutdownTokenSource.cs +++ b/src/Tgstation.Server.Host/System/ProgramShutdownTokenSource.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.System public CancellationToken Token => cancellationTokenSource?.Token ?? default; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// public ProgramShutdownTokenSource() { @@ -55,4 +55,4 @@ namespace Tgstation.Server.Host.System } } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs index 4fb35a71a8..e97dbb1aa9 100644 --- a/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs @@ -1,13 +1,15 @@ -using BetterWin32Errors; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; + +using BetterWin32Errors; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.System @@ -16,40 +18,46 @@ namespace Tgstation.Server.Host.System sealed class WindowsNetworkPromptReaper : IHostedService, INetworkPromptReaper, IDisposable { /// - /// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window + /// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window. /// const int SendMessageCount = 5; /// - /// Check for prompts each time this amount of milliseconds pass + /// Check for prompts each time this amount of milliseconds pass. /// const int RecheckDelayMs = 250; /// - /// The for the + /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// - /// The for the + /// The for the . /// readonly ILogger logger; /// - /// The for the + /// The for the . /// readonly CancellationTokenSource cancellationTokenSource; /// - /// The list of s registered + /// The list of s registered. /// readonly List registeredProcesses; /// - /// The representing the lifetime of the + /// The representing the lifetime of the . /// Task runTask; + /// + /// Callback for . + /// + /// The window handle. + /// Unused. + /// if enumeration should continue, otherwise. static bool EnumWindow(IntPtr hWnd, IntPtr lParam) { var gcChildhandlesList = GCHandle.FromIntPtr(lParam); @@ -63,7 +71,12 @@ namespace Tgstation.Server.Host.System return true; } - static List GetAllChildHandles(IntPtr main) + /// + /// Get all the child windows handles of a given . + /// + /// The handle of the window to enumerate. + /// A of all the child handles of . + static List GetAllChildHandles(IntPtr mainWindow) { var childHandles = new List(); var gcChildhandlesList = GCHandle.Alloc(childHandles); @@ -71,7 +84,7 @@ namespace Tgstation.Server.Host.System { var pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList); NativeMethods.EnumWindowProc childProc = new NativeMethods.EnumWindowProc(EnumWindow); - NativeMethods.EnumChildWindows(main, childProc, pointerChildHandlesList); + NativeMethods.EnumChildWindows(mainWindow, childProc, pointerChildHandlesList); } finally { @@ -82,10 +95,10 @@ namespace Tgstation.Server.Host.System } /// - /// Construct a + /// Initializes a new instance of the class. /// - /// The value of - /// The value of + /// The value of . + /// The value of . public WindowsNetworkPromptReaper(IAsyncDelayer asyncDelayer, ILogger logger) { this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); @@ -98,76 +111,6 @@ namespace Tgstation.Server.Host.System /// public void Dispose() => cancellationTokenSource.Dispose(); - async Task Run(CancellationToken cancellationToken) - { - logger.LogDebug("Starting network prompt reaper..."); - try - { - while (!cancellationToken.IsCancellationRequested) - { - await asyncDelayer.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); - - IntPtr window; - int processId; - lock (registeredProcesses) - { - if (registeredProcesses.Count == 0) - continue; - - window = NativeMethods.FindWindow(null, "Network Accessibility"); - if (window == IntPtr.Zero) - continue; - - // found a bitch - var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId); - if (!registeredProcesses.Any(x => x.Id == processId)) - continue; // not our bitch - } - - logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId); - - var found = false; - foreach (var I in GetAllChildHandles(window)) - { - const int MaxLength = 10; - var stringBuilder = new StringBuilder(MaxLength + 1); - - if (NativeMethods.GetWindowText(I, stringBuilder, MaxLength) == 0) - { - logger.LogWarning(new Win32Exception(), "Error calling GetWindowText!"); - continue; - } - - var windowText = stringBuilder.ToString(); - if (windowText == "Yes") - { - // smash_button_meme.jpg - logger.LogTrace("Sending \"Yes\" button clicks..."); - for (var J = 0; J < SendMessageCount; ++J) - { - const int BM_CLICK = 0x00F5; - var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero); - } - - found = true; - break; - } - } - - if (!found) - logger.LogDebug("Unable to find \"Yes\" button for \"Network Accessibility\" window in owned process {0}!", processId); - } - } - catch (OperationCanceledException ex) - { - logger.LogTrace(ex, "Cancelled!"); - } - finally - { - logger.LogDebug("Exiting network prompt reaper..."); - } - } - /// public Task StartAsync(CancellationToken cancellationToken) { @@ -198,12 +141,88 @@ namespace Tgstation.Server.Host.System registeredProcesses.Add(process); } - process.Lifetime.ContinueWith(x => + process.Lifetime.ContinueWith( + x => { logger.LogTrace("Unregistering process {0}...", process.Id); lock (registeredProcesses) registeredProcesses.Remove(process); }, TaskScheduler.Current); } + + /// + /// Main loop for the . + /// + /// The for the operation. + /// A representing the running operation. + async Task Run(CancellationToken cancellationToken) + { + logger.LogDebug("Starting network prompt reaper..."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + await asyncDelayer.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); + + IntPtr window; + int processId; + lock (registeredProcesses) + { + if (registeredProcesses.Count == 0) + continue; + + window = NativeMethods.FindWindow(null, "Network Accessibility"); + if (window == IntPtr.Zero) + continue; + + // found a bitch + var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId); + if (!registeredProcesses.Any(x => x.Id == processId)) + continue; // not our bitch + } + + logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId); + + var found = false; + foreach (var childHandle in GetAllChildHandles(window)) + { + const int MaxLength = 10; + var stringBuilder = new StringBuilder(MaxLength + 1); + + if (NativeMethods.GetWindowText(childHandle, stringBuilder, MaxLength) == 0) + { + logger.LogWarning(new Win32Exception(), "Error calling GetWindowText!"); + continue; + } + + var windowText = stringBuilder.ToString(); + if (windowText == "Yes") + { + // smash_button_meme.jpg + logger.LogTrace("Sending \"Yes\" button clicks..."); + for (var iteration = 0; iteration < SendMessageCount; ++iteration) + { + const int BM_CLICK = 0x00F5; + var result = NativeMethods.SendMessage(childHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero); + } + + found = true; + break; + } + } + + if (!found) + logger.LogDebug("Unable to find \"Yes\" button for \"Network Accessibility\" window in owned process {0}!", processId); + } + } + catch (OperationCanceledException ex) + { + logger.LogTrace(ex, "Cancelled!"); + } + finally + { + logger.LogDebug("Exiting network prompt reaper..."); + } + } } } diff --git a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index f200be8192..d8c66615f2 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -1,11 +1,13 @@ -using BetterWin32Errors; -using System; +using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Management; using System.Threading; using System.Threading.Tasks; + +using BetterWin32Errors; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index de6fb4e86a..5789898241 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -11,7 +11,7 @@ true bin\$(Configuration)\netcoreapp3.1\Tgstation.Server.Host.xml true - API1000 + API1000,IDE0004 @@ -92,6 +92,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Host/Transfer/FileDownloadProvider.cs b/src/Tgstation.Server.Host/Transfer/FileDownloadProvider.cs index 16f78aad1a..6ed973dd9a 100644 --- a/src/Tgstation.Server.Host/Transfer/FileDownloadProvider.cs +++ b/src/Tgstation.Server.Host/Transfer/FileDownloadProvider.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Transfer @@ -32,7 +33,7 @@ namespace Tgstation.Server.Host.Transfer public bool ShareWrite { get; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The optional value of . diff --git a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs index b83a2d2463..2e5a9b5eb7 100644 --- a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs +++ b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Core; @@ -69,7 +71,7 @@ namespace Tgstation.Server.Host.Transfer Task expireTask; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . /// The value of . @@ -113,42 +115,6 @@ namespace Tgstation.Server.Host.Transfer await toAwait.ConfigureAwait(false); } - /// - /// Creates a new . - /// - /// A new . - FileTicketResponse CreateTicket() => new FileTicketResponse - { - FileTicket = cryptographySuite.GetSecureString() - }; - - void QueueExpiry(Action expireAction) - { - Task oldExpireTask; - async Task ExpireAsync() - { - var expireAt = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(TicketValidityMinutes); - try - { - await oldExpireTask.WithToken(disposeCts.Token).ConfigureAwait(false); - - var now = DateTimeOffset.UtcNow; - if (now < expireAt) - await asyncDelayer.Delay(expireAt - now, disposeCts.Token).ConfigureAwait(false); - } - finally - { - expireAction(); - } - } - - lock (synchronizationLock) - { - oldExpireTask = expireTask; - expireTask = ExpireAsync(); - } - } - /// public FileTicketResponse CreateDownload(FileDownloadProvider downloadProvider) { @@ -164,7 +130,7 @@ namespace Tgstation.Server.Host.Transfer QueueExpiry(() => { lock (downloadTickets) - if(downloadTickets.Remove(ticketResult.FileTicket)) + if (downloadTickets.Remove(ticketResult.FileTicket)) logger.LogTrace("Expired download ticket {0}...", ticketResult.FileTicket); }); @@ -237,7 +203,7 @@ namespace Tgstation.Server.Host.Transfer null, new ErrorMessageResponse(ErrorCode.IOError) { - AdditionalData = ex.ToString() + AdditionalData = ex.ToString(), }); } @@ -273,5 +239,45 @@ namespace Tgstation.Server.Host.Transfer return await uploadProvider.Completion(stream, cancellationToken).ConfigureAwait(false); } + + /// + /// Creates a new . + /// + /// A new . + FileTicketResponse CreateTicket() => new FileTicketResponse + { + FileTicket = cryptographySuite.GetSecureString(), + }; + + /// + /// Queue an to run after . + /// + /// The to take after . + void QueueExpiry(Action expireAction) + { + Task oldExpireTask; + async Task ExpireAsync() + { + var expireAt = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(TicketValidityMinutes); + try + { + await oldExpireTask.WithToken(disposeCts.Token).ConfigureAwait(false); + + var now = DateTimeOffset.UtcNow; + if (now < expireAt) + await asyncDelayer.Delay(expireAt - now, disposeCts.Token).ConfigureAwait(false); + } + finally + { + expireAction(); + } + } + + lock (synchronizationLock) + { + oldExpireTask = expireTask; + expireTask = ExpireAsync(); + } + } } } diff --git a/src/Tgstation.Server.Host/Transfer/FileUploadProvider.cs b/src/Tgstation.Server.Host/Transfer/FileUploadProvider.cs index 286e86d62c..f33e3e7e3f 100644 --- a/src/Tgstation.Server.Host/Transfer/FileUploadProvider.cs +++ b/src/Tgstation.Server.Host/Transfer/FileUploadProvider.cs @@ -1,8 +1,10 @@ -using Microsoft.AspNetCore.WebUtilities; -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; + +using Microsoft.AspNetCore.WebUtilities; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Extensions; @@ -42,10 +44,10 @@ namespace Tgstation.Server.Host.Transfer ErrorMessageResponse errorMessage; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The value of . - /// The value of + /// The value of . public FileUploadProvider(FileTicketResponse ticket, bool requireSynchronousIO) { Ticket = ticket ?? throw new ArgumentNullException(nameof(ticket)); diff --git a/src/Tgstation.Server.Host/Transfer/IFileTransferStreamHandler.cs b/src/Tgstation.Server.Host/Transfer/IFileTransferStreamHandler.cs index 77c861c865..b5353b7140 100644 --- a/src/Tgstation.Server.Host/Transfer/IFileTransferStreamHandler.cs +++ b/src/Tgstation.Server.Host/Transfer/IFileTransferStreamHandler.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Transfer diff --git a/src/Tgstation.Server.Host/Transfer/IFileTransferTicketProvider.cs b/src/Tgstation.Server.Host/Transfer/IFileTransferTicketProvider.cs index dd847774fb..0563d039b8 100644 --- a/src/Tgstation.Server.Host/Transfer/IFileTransferTicketProvider.cs +++ b/src/Tgstation.Server.Host/Transfer/IFileTransferTicketProvider.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Models.Response; +using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Transfer { diff --git a/src/Tgstation.Server.Host/Transfer/IFileUploadTicket.cs b/src/Tgstation.Server.Host/Transfer/IFileUploadTicket.cs index 622bc90235..f6e56a6b33 100644 --- a/src/Tgstation.Server.Host/Transfer/IFileUploadTicket.cs +++ b/src/Tgstation.Server.Host/Transfer/IFileUploadTicket.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models.Response; namespace Tgstation.Server.Host.Transfer diff --git a/tests/Tgstation.Server.Api.Tests/Models/TestErrorCodeExtensions.cs b/tests/Tgstation.Server.Api.Tests/Models/TestErrorCodeExtensions.cs index bdf09285f9..acd9f0542e 100644 --- a/tests/Tgstation.Server.Api.Tests/Models/TestErrorCodeExtensions.cs +++ b/tests/Tgstation.Server.Api.Tests/Models/TestErrorCodeExtensions.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Tgstation.Server.Api.Models.Tests { /// - /// Tests for the . + /// Tests for the class. /// [TestClass] public sealed class TestErrorCodeExtensions diff --git a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs index dee8aa111f..0f03ab7ccb 100644 --- a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs +++ b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs @@ -23,7 +23,7 @@ namespace Tgstation.Server.Api.Rights.Tests Assert.AreEqual(ulongType, Enum.GetUnderlyingType(rightType)); var asUlong = (ulong)J; var isOne = asUlong == 1; - if(!isOne) + if (!isOne) Assert.AreEqual(0U, asUlong % 2, String.Format("Enum {0} of {1} is not a power of 2!", Enum.GetName(rightType, asUlong), rightType)); if (expectedLog > -1) diff --git a/tests/Tgstation.Server.Api.Tests/TestApiHeaders.cs b/tests/Tgstation.Server.Api.Tests/TestApiHeaders.cs index 8a310fb869..b4734c2060 100644 --- a/tests/Tgstation.Server.Api.Tests/TestApiHeaders.cs +++ b/tests/Tgstation.Server.Api.Tests/TestApiHeaders.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Net.Http.Headers; using System.Net.Mime; + using Tgstation.Server.Api.Models; namespace Tgstation.Server.Api.Tests diff --git a/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs b/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs index 91c762787b..1690dcde97 100644 --- a/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs +++ b/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; diff --git a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs index 5a5ce5aebb..9e5d8eaabf 100644 --- a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs +++ b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; diff --git a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs index 5eca82b110..17cfa1f572 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs @@ -4,6 +4,7 @@ using Moq; using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Console.Tests diff --git a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs index 91a32ff3fe..7c16af5646 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs +++ b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs @@ -1,10 +1,11 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service.Tests diff --git a/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs b/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs index 5a147abef5..c73d8aa050 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs @@ -1,8 +1,9 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond.Tests @@ -41,7 +42,7 @@ namespace Tgstation.Server.Host.Components.Byond.Tests var mockPostWriteHandler = new Mock(); var mockLogger = new Mock>(); var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object); - + await Assert.ThrowsExceptionAsync(() => installer.DownloadVersion(null, default)).ConfigureAwait(false); var ourArray = Array.Empty(); @@ -60,7 +61,7 @@ namespace Tgstation.Server.Host.Components.Byond.Tests var mockPostWriteHandler = new Mock(); var mockLogger = new Mock>(); var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object); - + const string FakePath = "fake"; await Assert.ThrowsExceptionAsync(() => installer.InstallByond(null, null, default)).ConfigureAwait(false); await Assert.ThrowsExceptionAsync(() => installer.InstallByond(FakePath, null, default)).ConfigureAwait(false); diff --git a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestDiscordProvider.cs b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestDiscordProvider.cs index a035eee34c..10387e5095 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestDiscordProvider.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestDiscordProvider.cs @@ -5,6 +5,7 @@ using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.System; @@ -21,7 +22,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests public void Initialize() { var actualToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN"); - if(!String.IsNullOrWhiteSpace(actualToken)) + if (!String.IsNullOrWhiteSpace(actualToken)) testToken1 = new ChatBot { ConnectionString = actualToken, diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs index 6652345fab..4f9e450150 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs @@ -5,7 +5,7 @@ using System; namespace Tgstation.Server.Host.Components.Events.Tests { /// - /// Tests for the . + /// Tests for the class. /// [TestClass] public sealed class TestEventScriptAttribute diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs index 3e9ed9b8c0..9c1a467160 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs @@ -12,7 +12,8 @@ namespace Tgstation.Server.Host.Components.Events.Tests public void TestAllEventTypesHaveUniqueEventScriptAttributes() { var allScripts = new HashSet(); - foreach (var eventType in Enum.GetValues(typeof(EventType))) { + foreach (var eventType in Enum.GetValues(typeof(EventType))) + { var list = typeof(EventType) .GetField(eventType.ToString()) .GetCustomAttributes(false) @@ -23,6 +24,6 @@ namespace Tgstation.Server.Host.Components.Events.Tests var attribute = list.First(); Assert.IsTrue(allScripts.Add(attribute.ScriptName), $"Non-unique script Name: {attribute.ScriptName}"); } - } + } } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 672fadd9c9..e688ced173 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading; + using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Security; diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs b/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs index 6c3b29c467..0c895bbdf3 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs @@ -5,6 +5,7 @@ using Octokit; using System; using System.Net.Http.Headers; using System.Threading.Tasks; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.System; diff --git a/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs index c95662427b..76d5f2683a 100644 --- a/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs @@ -1,7 +1,8 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MySql.Data.MySqlClient; using System; using System.Data.SqlClient; + using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database.Tests diff --git a/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs index 14e96f8316..ac18c12321 100644 --- a/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs @@ -1,8 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading.Tasks; + using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Database.Tests diff --git a/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs b/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs index 39d2eee434..8347ca19f7 100644 --- a/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs +++ b/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; + using Tgstation.Server.Api; + using YamlDotNet.Serialization; namespace Tgstation.Server.Host.Extensions.Converters.Tests diff --git a/tests/Tgstation.Server.Host.Tests/IO/TestConsole.cs b/tests/Tgstation.Server.Host.Tests/IO/TestConsole.cs index 59335e2425..12ef4f3000 100644 --- a/tests/Tgstation.Server.Host.Tests/IO/TestConsole.cs +++ b/tests/Tgstation.Server.Host.Tests/IO/TestConsole.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.IO.Tests await console.WriteAsync(null, true, default).ConfigureAwait(false); await console.WriteAsync(String.Empty, false, default).ConfigureAwait(true); } - catch(InvalidOperationException) + catch (InvalidOperationException) { Assert.IsFalse(console.Available); } diff --git a/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs index a4143ca8cc..3ddcfc1fe4 100644 --- a/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Jobs.Tests var tcs = new TaskCompletionSource(); currentWaitTask = tcs.Task; cancelled = false; - using(var handler = new JobHandler(TestJob)) + using (var handler = new JobHandler(TestJob)) { handler.Start(); handler.Cancel(); diff --git a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs index f97b7c0795..090f10f1ec 100644 --- a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs +++ b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; + using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; diff --git a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs index f2d258f504..fbca0ef3d0 100644 --- a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs @@ -12,6 +12,7 @@ using System.Data.Common; using System.Text; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; diff --git a/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs b/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs index 7dabedf9d2..93d5acde14 100644 --- a/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs @@ -1,9 +1,10 @@ -using Castle.Core.Logging; +using Castle.Core.Logging; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.System.Tests diff --git a/tests/Tgstation.Server.Host.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Tests/TestProgram.cs index 634c18be97..cbeabc011e 100644 --- a/tests/Tgstation.Server.Host.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Tests/TestProgram.cs @@ -1,9 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Tests diff --git a/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs index 6039e4e8f7..ccff68e20d 100644 --- a/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading.Tasks; + using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; diff --git a/tests/Tgstation.Server.Tests/AdministrationTest.cs b/tests/Tgstation.Server.Tests/AdministrationTest.cs index 2e54278069..2ea67f7c8d 100644 --- a/tests/Tgstation.Server.Tests/AdministrationTest.cs +++ b/tests/Tgstation.Server.Tests/AdministrationTest.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; diff --git a/tests/Tgstation.Server.Tests/ApiAssert.cs b/tests/Tgstation.Server.Tests/ApiAssert.cs index 0689fe51d2..bdae5c1375 100644 --- a/tests/Tgstation.Server.Tests/ApiAssert.cs +++ b/tests/Tgstation.Server.Tests/ApiAssert.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Client; diff --git a/tests/Tgstation.Server.Tests/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs index 03b4774087..afaa705ea9 100644 --- a/tests/Tgstation.Server.Tests/Instance/ByondTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; diff --git a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs index e6c99c511a..8c1baf40df 100644 --- a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Client; @@ -67,7 +68,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(true, updatedBot.Enabled); - var channelId = Environment.GetEnvironmentVariable("TGS4_TEST_IRC_CHANNEL");; + var channelId = Environment.GetEnvironmentVariable("TGS4_TEST_IRC_CHANNEL"); ; updatedBot = await chatClient.Update(new ChatBotUpdateRequest { diff --git a/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs index a368fcd616..7b82c37788 100644 --- a/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs @@ -1,4 +1,3 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Net.Http; @@ -7,6 +6,9 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Client; diff --git a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs index 0c78271c88..2db5b8dec8 100644 --- a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs @@ -2,6 +2,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Client; diff --git a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs index dc210dd6da..f56c8ffac7 100644 --- a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Client; using Tgstation.Server.Client.Components; diff --git a/tests/Tgstation.Server.Tests/Instance/JobsRequiredTest.cs b/tests/Tgstation.Server.Tests/Instance/JobsRequiredTest.cs index 3532e83cfd..f10a6efc2c 100644 --- a/tests/Tgstation.Server.Tests/Instance/JobsRequiredTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/JobsRequiredTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client.Components; diff --git a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs index 7ce4dac454..0f9ebcadce 100644 --- a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -165,7 +166,7 @@ namespace Tgstation.Server.Tests.Instance Assert.IsNotNull(numberOnlyMerging.ActiveJob); Assert.IsTrue(numberOnlyMerging.ActiveJob.Description.Contains(prNumber.ToString())); - await WaitForJob(numberOnlyMerging.ActiveJob, 20, false, null,cancellationToken); + await WaitForJob(numberOnlyMerging.ActiveJob, 20, false, null, cancellationToken); var withMerge = await repositoryClient.Read(cancellationToken); Assert.AreEqual(repository.Reference, withMerge.Reference); diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 3df1e9b84b..3159dd1be4 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -1,4 +1,4 @@ -using Byond.TopicSender; +using Byond.TopicSender; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -9,6 +9,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -435,7 +436,7 @@ namespace Tgstation.Server.Tests.Instance { global::System.Console.WriteLine("TEST: WATCHDOG STARTING ENDLESS"); var dd = await instanceClient.DreamDaemon.Read(cancellationToken); - if(dd.ActiveCompileJob == null) + if (dd.ActiveCompileJob == null) await DeployTestDme("LongRunning/long_running_test", DreamDaemonSecurity.Trusted, true, cancellationToken); var startJob = await StartDD(cancellationToken).ConfigureAwait(false); diff --git a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs index 82d99d3ed9..14a50233c5 100644 --- a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs +++ b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index a61532bca8..ff975168ad 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -19,6 +19,7 @@ using System.Net.Sockets; using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -533,7 +534,7 @@ namespace Tgstation.Server.Tests async Task CreateAdminClient(Uri url, CancellationToken cancellationToken) { var giveUpAt = DateTimeOffset.UtcNow.AddMinutes(2); - for(var I = 1; ; ++I) + for (var I = 1; ; ++I) { try { @@ -712,7 +713,7 @@ namespace Tgstation.Server.Tests public async Task TestServer() { var procs = System.Diagnostics.Process.GetProcessesByName("byond"); - if(procs.Any()) + if (procs.Any()) { foreach (var proc in procs) proc.Dispose(); @@ -887,7 +888,7 @@ namespace Tgstation.Server.Tests await new InstanceManagerTest(adminClient.Instances, adminClient.Users, server.Directory).RunPostTest(cancellationToken); } } - catch(ApiException ex) + catch (ApiException ex) { Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex.ErrorCode}: {ex.Message}\n{ex.AdditionalServerData}"); throw; diff --git a/tests/Tgstation.Server.Tests/RawRequestTests.cs b/tests/Tgstation.Server.Tests/RawRequestTests.cs index 177755b874..78d4d40345 100644 --- a/tests/Tgstation.Server.Tests/RawRequestTests.cs +++ b/tests/Tgstation.Server.Tests/RawRequestTests.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index b63a259fe3..9fdbe31d62 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Host; using Tgstation.Server.Host.Configuration; diff --git a/tests/Tgstation.Server.Tests/UsersTest.cs b/tests/Tgstation.Server.Tests/UsersTest.cs index 625b0cbc89..cdded81e86 100644 --- a/tests/Tgstation.Server.Tests/UsersTest.cs +++ b/tests/Tgstation.Server.Tests/UsersTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; diff --git a/tests/Tgstation.Server.Tests/VersionsTest.cs b/tests/Tgstation.Server.Tests/VersionsTest.cs index 1e769ac96f..4098b0f81e 100644 --- a/tests/Tgstation.Server.Tests/VersionsTest.cs +++ b/tests/Tgstation.Server.Tests/VersionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; + using Tgstation.Server.Api; using Tgstation.Server.Client; using Tgstation.Server.Host; @@ -191,7 +192,7 @@ namespace Tgstation.Server.Tests var migrationType = kvp.Key; var migrationTimestamp = kvp.Value; - switch(migrationType.Name.Substring(0, 2)) + switch (migrationType.Name.Substring(0, 2)) { case "MS": if (String.Compare(GetMigrationTimestampString(latestMigrationMS), migrationTimestamp) < 0) diff --git a/tools/ReleaseNotes/Program.cs b/tools/ReleaseNotes/Program.cs index 601860250a..099015d912 100644 --- a/tools/ReleaseNotes/Program.cs +++ b/tools/ReleaseNotes/Program.cs @@ -108,7 +108,7 @@ namespace ReleaseNotes milestoneTask = GetMilestone(); // if (!fullPR.Merged) - //return; + //return; async Task BuildNotesFromComment(string comment, User user) { @@ -261,7 +261,7 @@ namespace ReleaseNotes var controlPanelVersionsPropertyGroup = project2.Elements().First(x => x.Name == controlPanelXmlNamespace + "PropertyGroup"); var coreVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsCoreVersion").Value); - if(coreVersion != version) + if (coreVersion != version) { Console.WriteLine("Received a different version on command line than in Version.props!"); return 10;