mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Merge branch 'master' of https://github.com/tgstation/tgstation-server into thingymajig
This commit is contained in:
+22
-1
@@ -1,9 +1,30 @@
|
||||
.dockerignore
|
||||
.git
|
||||
.github
|
||||
.gitignore
|
||||
.vs
|
||||
.vscode
|
||||
packages
|
||||
.travis.yml
|
||||
.codecov.yml
|
||||
appveyor.yml
|
||||
LICENSE
|
||||
README
|
||||
build/**
|
||||
!build/analyzers.ruleset
|
||||
!build/tgs.docker.sh
|
||||
!build/tgs.ico
|
||||
!build/stylecop.json
|
||||
*/bin
|
||||
*/obj
|
||||
tests
|
||||
docs
|
||||
tools
|
||||
src/DMAPI
|
||||
src/Tgstation.Server.Host/ClientApp/build
|
||||
src/Tgstation.Server.Host/ClientApp/node_modules
|
||||
src/Tgstation.Server.Host/wwwroot
|
||||
src/Tgstation.Server.Host/appsettings.Development.json
|
||||
src/Tgstation.Server.Host/tgs.bat
|
||||
src/Tgstation.Server.Host/tgs.sh
|
||||
src/Tgstation.Server.Host.Client
|
||||
src/Tgstation.Server.Host.Service
|
||||
|
||||
+27
-7
@@ -34,7 +34,7 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co
|
||||
|
||||
### Development Environment
|
||||
|
||||
You need the Dotnet 2.1 SDK to compile the main server and command line programs. In order to build the service version you also need a .NET 4.7.1 build chain
|
||||
You need the Dotnet 2.1 SDK and npm>=v5.7 (in your PATH) to compile the server. In order to build the service version you also need a .NET 4.7.1 build chain
|
||||
|
||||
The recommended IDE is Visual Studio 2017 which has installation options for both of these.
|
||||
|
||||
@@ -47,11 +47,6 @@ As mentioned before, you are expected to follow these specifications in order to
|
||||
### Object Oriented Code
|
||||
As C# is an object-oriented language, code must be object-oriented when possible in order to be more flexible when adding content to it. If you don't know what "object-oriented" means, we highly recommend you do some light research to grasp the basics.
|
||||
|
||||
### Tabs, not spaces
|
||||
You must use tabs to indent your code, NOT SPACES.
|
||||
|
||||
(You may use spaces to align something, but you should tab to the block level first, then add the remaining spaces)
|
||||
|
||||
### No hacky code
|
||||
Hacky code, such as adding specific checks, is highly discouraged and only allowed when there is ***no*** other option. (Protip: 'I couldn't immediately think of a proper way so thus there must be no other option' is not gonna cut it here! If you can't think of anything else, say that outright and admit that you need help with it. Maintainers exist for exactly that reason.)
|
||||
|
||||
@@ -97,7 +92,7 @@ The version format we use is 4.\<major\>.\<minor\>.\<patch\>. The first number n
|
||||
|
||||
### Formatting
|
||||
|
||||
The formatting style is the same one Visual studio uses by default. Quick formatting of code blocks can be achieved by deleting and retyping the trailing `}`
|
||||
Stylecop will throw warnings if your code does not match style guidelines. Do NOT suppress these
|
||||
|
||||
### Use early return
|
||||
Do not enclose a function in an if-block when returning on a condition is more feasible
|
||||
@@ -149,7 +144,32 @@ There is no strict process when it comes to merging pull requests. Pull requests
|
||||
|
||||
* Commits MUST be properly titled and commented as we only use merge commits for the pull request process
|
||||
|
||||
## Deployment process
|
||||
|
||||
Every issue/pull request in a release should share a common milestone named with the release version i.e. `4.5.3.5`
|
||||
|
||||
When every issue and PR in the milestone is closed. Create a version bump PR that changes the version numbers. At the time of this writing they exist in the following files
|
||||
|
||||
- `/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj`
|
||||
- `/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj`
|
||||
- 2 in `/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs`
|
||||
- `/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj`
|
||||
|
||||
Merge the pull request with `[TGSDeploy]` somewhere in the commit title. The scripts will handle amalgamating release notes, building, closing the milestone, and publishing the release. This will also trigger update notifications on existing TGS deployments.
|
||||
|
||||
### API/Client Deployment
|
||||
|
||||
The Api/Client project versions must be updated on nuget when changed. The numbers exist in the following files:
|
||||
|
||||
- `/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj`
|
||||
- `/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj`
|
||||
|
||||
These should not be the same numbers as the main suite. When bumping the API version the client should only receive a minor (3rd number) version bump unless major changes to CLIENT code were made.
|
||||
|
||||
Merge these alongside regular deployments with `[NugetDeploy]` in the commit title (Won't work without an accompanying `[TGSDeploy]`). This will handle the nuget publishing.
|
||||
|
||||
## Banned content
|
||||
|
||||
Do not add any of the following in a Pull Request or risk getting the PR closed:
|
||||
* National Socialist Party of Germany content, National Socialist Party of Germany related content, or National Socialist Party of Germany references
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
If your issue is for the web control panel, please report it here instead: https://github.com/tgstation/tgstation-server-control-panel
|
||||
|
||||
Please include:
|
||||
|
||||
- A description of the issue
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[Release Notes]: # (Your PR should contain a detailed list of notable changes, titled appropriately. This includes any observable changes to the server or DMAPI. See example below)
|
||||
|
||||
:cl:
|
||||
Description of your change
|
||||
Each newline corresponds to a release note in the upcoming sprint
|
||||
/:cl:
|
||||
|
||||
:cl:
|
||||
You can also have multiple sets of release notes per pull request
|
||||
They will be amalgamated together in the end
|
||||
/:cl:
|
||||
|
||||
[Why]: # (Please add a short description [two lines down] of why you think these changes would benefit the game. If you can't justify it in words, it might not be worth adding.)
|
||||
@@ -15,3 +15,7 @@ artifacts/
|
||||
/tests/DMAPI/travistester.int
|
||||
/tests/DMAPI/travistester.dmb
|
||||
/src/Tgstation.Server.Host/appsettings.Development.json
|
||||
/src/Tgstation.Server.Host/wwwroot
|
||||
/src/Tgstation.Server.Host/ClientApp/node_modules
|
||||
/src/Tgstation.Server.Host/ClientApp/build
|
||||
/tools/ReleaseNotes/release_notes.md
|
||||
|
||||
@@ -92,6 +92,12 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi
|
||||
|
||||
- `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide ([You should probably use '127.0.0.1' instead of 'localhost'](https://stackoverflow.com/questions/19712307/mysql-localhost-127-0-0-1)).
|
||||
|
||||
- `ControlPanel:Enable`: Enable the javascript based control panel to be served from the server via /index.html
|
||||
|
||||
- `ControlPanel:AllowAnyOrigin`: Set the Access-Control-Allow-Origin header to * for all responses (also enables all headers and methods)
|
||||
|
||||
- `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin`
|
||||
|
||||
### Database Configuration
|
||||
|
||||
If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk.
|
||||
|
||||
+9
-5
@@ -6,6 +6,8 @@ environment:
|
||||
TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12!
|
||||
TGS4_TEST_GITHUB_TOKEN:
|
||||
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
|
||||
TGS_RELEASE_NOTES_TOKEN:
|
||||
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -24,6 +26,8 @@ artifacts:
|
||||
name: ServerUpdatePackage
|
||||
- path: src/DMAPI
|
||||
name: DMAPI
|
||||
- path: tgsdox
|
||||
name: DocumentationHtml
|
||||
cache:
|
||||
- ~\.nuget\packages -> **\*.csproj
|
||||
- C:\ProgramData\chocolatey\bin -> appveyor.yml
|
||||
@@ -33,6 +37,7 @@ services:
|
||||
install:
|
||||
- choco install doxygen.portable codecov graphviz.portable opencover.portable
|
||||
- nuget restore tgstation-server.sln
|
||||
- ps: Install-Product node 10
|
||||
build:
|
||||
project: tgstation-server.sln
|
||||
parallel: false
|
||||
@@ -77,17 +82,16 @@ after_test:
|
||||
- ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/
|
||||
- ps: Remove-Item artifacts/ServerHost/appsettings.json
|
||||
#deploy stuff
|
||||
- ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion
|
||||
- ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ if($env:CONFIGURATION -match "Release"){ $env:TGSDeploy = "Do it." }}}
|
||||
- ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[NugetDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ if($env:CONFIGURATION -match "Release"){ $env:NugetDeploy = "Do it." }}}
|
||||
- ps: build/prep_deployment.ps1
|
||||
deploy:
|
||||
- provider: GitHub
|
||||
release: "tgstation-server-v$(TGSVersion)"
|
||||
description: 'The /tg/station server suite'
|
||||
description: "$(TGSReleaseNotes)"
|
||||
auth_token:
|
||||
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
|
||||
artifact: ServerConsole,ServerService,ServerUpdatePackage,DMAPI
|
||||
draft: true
|
||||
draft: $(TGSDraftNotes)
|
||||
prerelease: true
|
||||
on:
|
||||
TGSDeploy: "Do it."
|
||||
- provider: NuGet
|
||||
|
||||
@@ -32,4 +32,6 @@ if($publish_dox){
|
||||
git commit -m "Deploy code docs to GitHub Pages for Appveyor build $Env:APPVEYOR_BUILD_NUMBER" -m "Commit: $Env:APPVEYOR_REPO_COMMIT"
|
||||
git push -f "https://$Env:TGS4_TEST_GITHUB_TOKEN@$github_url" 2>&1 | out-null
|
||||
cd "$bf"
|
||||
rm -r "$doxdir/.git"
|
||||
}
|
||||
mv C:/tgsdox "$bf/tgsdox"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
FROM microsoft/dotnet:2.1-sdk AS build
|
||||
|
||||
# install node and npm
|
||||
# replace shell with bash so we can source files
|
||||
RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | sh
|
||||
|
||||
ENV NODE_VERSION=10.13.0
|
||||
ENV NVM_DIR /root/.nvm
|
||||
|
||||
RUN . $NVM_DIR/nvm.sh \
|
||||
&& nvm install $NODE_VERSION \
|
||||
&& nvm use $NODE_VERSION \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
dos2unix \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
|
||||
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY tgstation-server.sln ./
|
||||
@@ -13,6 +31,9 @@ RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=fa
|
||||
|
||||
COPY . .
|
||||
|
||||
#run dos2unix on tgs.docker.sh so we can build without issue on windows
|
||||
RUN dos2unix build/tgs.docker.sh
|
||||
|
||||
WORKDIR /src/src/Tgstation.Server.Host.Console
|
||||
RUN dotnet publish -c Release -o /app
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
$env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion
|
||||
|
||||
Write-Host "TGS Version: $env:TGSVersion"
|
||||
|
||||
if (($env:CONFIGURATION -match "Release") -And ($env:APPVEYOR_REPO_BRANCH -match "master") -And ($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]")) {
|
||||
Write-Host "Deploying..."
|
||||
$env:TGSDeploy = "Do it."
|
||||
|
||||
Write-Host "Generating release notes..."
|
||||
dotnet run -p "$env:APPVEYOR_BUILD_FOLDER/tools/ReleaseNotes" $env:TGSVersion
|
||||
$env:TGSDraftNotes = !($?)
|
||||
$releaseNotesPath = "$env:APPVEYOR_BUILD_FOLDER/tools/ReleaseNotes/release_notes.md"
|
||||
if (Test-Path $releaseNotesPath -PathType Leaf) {
|
||||
$env:TGSReleaseNotes = [IO.File]::ReadAllText($releaseNotesPath)
|
||||
}
|
||||
else {
|
||||
Write-Host "Release note generation failed, release will be created as a draft!"
|
||||
$env:TGSReleaseNotes = "Automatic generation failed, please fill manually!"
|
||||
}
|
||||
|
||||
if ($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[NugetDeploy\]") {
|
||||
$env:NugetDeploy = "Do it."
|
||||
Write-Host "Nuget deployment enabled"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"settings": {
|
||||
"indentation": {
|
||||
"tabSize": 4,
|
||||
"useTabs": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ dotnet tool install --global coverlet.console
|
||||
|
||||
mkdir TestResults
|
||||
|
||||
source ~/.nvm/nvm.sh && nvm install 10
|
||||
|
||||
cd tests/Tgstation.Server.Api.Tests
|
||||
|
||||
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
|
||||
|
||||
+6
-3
@@ -1,6 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
touch /config_data/appsettings.Production.json
|
||||
ln -s /config_data/appsettings.Production.json /app/appsettings.Production.json
|
||||
PROD_CONFIG=/config_data/appsettings.Production.json
|
||||
if [ ! -f $PROD_CONFIG ]; then
|
||||
echo "{}" > $PROD_CONFIG
|
||||
fi
|
||||
ln -s $PROD_CONFIG /app/appsettings.Production.json
|
||||
|
||||
exec dotnet Tgstation.Server.Host.Console.dll "$@"
|
||||
exec dotnet Tgstation.Server.Host.Console.dll
|
||||
|
||||
+1
-1
@@ -864,7 +864,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE = packages
|
||||
EXCLUDE = packages src/Tgstation.Server.Host/webroot src/Tgstation.Server.Host/ClientApp/node_modules src/Tgstation.Server.Host/ClientApp/build
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
|
||||
+21
-5
@@ -107,6 +107,22 @@
|
||||
var/commit //full sha of compiled commit
|
||||
var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch
|
||||
|
||||
//represents a version of tgstation-server
|
||||
/datum/tgs_version
|
||||
var/suite //The suite version, can be >=3
|
||||
|
||||
//this group of variables can be null to represent a wild card
|
||||
var/major //The major version
|
||||
var/minor //The minor version
|
||||
var/patch //The patch version
|
||||
|
||||
var/raw_parameter //The unparsed parameter
|
||||
var/deprefixed_parameter //The version only bit of raw_parameter
|
||||
|
||||
//if the tgs_version is a wildcard version
|
||||
/datum/tgs_version/proc/Wildcard()
|
||||
return
|
||||
|
||||
//represents a merge of a GitHub pull request
|
||||
/datum/tgs_revision_information/test_merge
|
||||
var/number //pull request number
|
||||
@@ -155,22 +171,22 @@
|
||||
|
||||
//FUNCTIONS
|
||||
|
||||
//Returns the respective string version of the API
|
||||
//Returns the respective supported /datum/tgs_version of the API
|
||||
/world/proc/TgsMaximumAPIVersion()
|
||||
return
|
||||
|
||||
/world/proc/TgsMinimumAPIVersion()
|
||||
return
|
||||
|
||||
//Gets the current version of the server tools running the server
|
||||
/world/proc/TgsVersion()
|
||||
return
|
||||
|
||||
//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise
|
||||
//No function below this succeeds if it returns FALSE
|
||||
/world/proc/TgsAvailable()
|
||||
return
|
||||
|
||||
//Gets the current /datum/tgs_version of the server tools running the server
|
||||
/world/proc/TgsVersion()
|
||||
return
|
||||
|
||||
/world/proc/TgsInstanceName()
|
||||
return
|
||||
|
||||
|
||||
+36
-41
@@ -1,24 +1,46 @@
|
||||
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
|
||||
var/current_api = TGS_READ_GLOBAL(tgs)
|
||||
if(current_api)
|
||||
TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])!")
|
||||
TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?")
|
||||
return
|
||||
|
||||
#ifdef TGS_V3_API
|
||||
minimum_required_security_level = TGS_SECURITY_TRUSTED
|
||||
#endif
|
||||
|
||||
var/tgs_version = world.params[TGS_VERSION_PARAMETER]
|
||||
if(!tgs_version)
|
||||
var/raw_parameter = world.params[TGS_VERSION_PARAMETER]
|
||||
if(!raw_parameter)
|
||||
return
|
||||
|
||||
var/path = SelectTgsApi(tgs_version)
|
||||
if(!path)
|
||||
TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.")
|
||||
var/datum/tgs_version/version = new(raw_parameter)
|
||||
if(!version.Valid(FALSE))
|
||||
TGS_ERROR_LOG("Failed to validate TGS version parameter: [raw_parameter]!")
|
||||
return
|
||||
|
||||
TGS_INFO_LOG("Activating API for version [tgs_version]")
|
||||
var/datum/tgs_api/new_api = new path
|
||||
var/api_datum
|
||||
switch(version.suite)
|
||||
if(3)
|
||||
#ifndef TGS_V3_API
|
||||
TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!")
|
||||
#else
|
||||
switch(version.major)
|
||||
if(2)
|
||||
api_datum = /datum/tgs_api/v3210
|
||||
#endif
|
||||
if(4)
|
||||
switch(version.major)
|
||||
if(0)
|
||||
api_datum = /datum/tgs_api/v4
|
||||
|
||||
if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > TgsMaximumAPIVersion())
|
||||
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
|
||||
api_datum = /datum/tgs_api/latest
|
||||
|
||||
if(!api_datum)
|
||||
TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
|
||||
return
|
||||
|
||||
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
|
||||
var/datum/tgs_api/new_api = new api_datum(version)
|
||||
|
||||
TGS_WRITE_GLOBAL(tgs, new_api)
|
||||
|
||||
@@ -27,40 +49,11 @@
|
||||
TGS_WRITE_GLOBAL(tgs, null)
|
||||
TGS_ERROR_LOG("Failed to activate API!")
|
||||
|
||||
/world/proc/SelectTgsApi(tgs_version)
|
||||
//remove the old 3.0 header
|
||||
tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "")
|
||||
|
||||
var/list/version_bits = splittext(tgs_version, ".")
|
||||
|
||||
var/super = text2num(version_bits[1])
|
||||
var/major = text2num(version_bits[2])
|
||||
var/minor = text2num(version_bits[3])
|
||||
var/patch = text2num(version_bits[4])
|
||||
|
||||
switch(super)
|
||||
if(3)
|
||||
#ifndef TGS_V3_API
|
||||
TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!")
|
||||
#else
|
||||
switch(major)
|
||||
if(2)
|
||||
return /datum/tgs_api/v3210
|
||||
#endif
|
||||
if(4)
|
||||
switch(major)
|
||||
if(0)
|
||||
return /datum/tgs_api/v4
|
||||
|
||||
if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion())
|
||||
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
|
||||
return /datum/tgs_api/latest
|
||||
|
||||
/world/TgsMaximumAPIVersion()
|
||||
return "4.0.0.0"
|
||||
return new /datum/tgs_version("4.0.x.x")
|
||||
|
||||
/world/TgsMinimumAPIVersion()
|
||||
return "3.2.0.0"
|
||||
return new /datum/tgs_version("3.2.0.0")
|
||||
|
||||
/world/TgsInitializationComplete()
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
@@ -90,7 +83,9 @@
|
||||
return TGS_READ_GLOBAL(tgs) != null
|
||||
|
||||
/world/TgsVersion()
|
||||
return world.params[TGS_VERSION_PARAMETER]
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
if(api)
|
||||
return api.version
|
||||
|
||||
/world/TgsInstanceName()
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
|
||||
|
||||
/datum/tgs_api
|
||||
var/datum/tgs_version/version
|
||||
|
||||
/datum/tgs_api/New(datum/tgs_version/version)
|
||||
. = ..()
|
||||
src.version = version
|
||||
|
||||
/datum/tgs_api/latest
|
||||
parent_type = /datum/tgs_api/v4
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/datum/tgs_version/New(raw_parameter)
|
||||
src.raw_parameter = raw_parameter
|
||||
deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "")
|
||||
var/list/version_bits = splittext(deprefixed_parameter, ".")
|
||||
|
||||
suite = text2num(version_bits[1])
|
||||
if(version_bits.len > 1)
|
||||
major = text2num(version_bits[2])
|
||||
if(version_bits.len > 2)
|
||||
minor = text2num(version_bits[3])
|
||||
if(version_bits.len == 4)
|
||||
patch = text2num(version_bits[4])
|
||||
|
||||
/datum/tgs_version/proc/Valid(allow_wildcards = FALSE)
|
||||
if(suite == null)
|
||||
return FALSE
|
||||
if(allow_wildcards)
|
||||
return TRUE
|
||||
return !Wildcard()
|
||||
|
||||
/datum/tgs_version/Wildcard()
|
||||
return major == null || minor == null || patch == null
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "core\_definitions.dm"
|
||||
#include "core\core.dm"
|
||||
#include "core\datum.dm"
|
||||
#include "core\tgs_version.dm"
|
||||
#ifdef TGS_V3_API
|
||||
#include "v3210\api.dm"
|
||||
#include "v3210\commands.dm"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
@@ -28,32 +28,32 @@ namespace Tgstation.Server.Api
|
||||
/// <summary>
|
||||
/// The <see cref="Username"/> header key
|
||||
/// </summary>
|
||||
const string usernameHeader = "Username";
|
||||
const string UsernameHeader = "Username";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceId"/> header key
|
||||
/// </summary>
|
||||
const string instanceIdHeader = "Instance";
|
||||
const string InstanceIdHeader = "Instance";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// </summary>
|
||||
const string jwtAuthenticationScheme = "Bearer";
|
||||
const string JwtAuthenticationScheme = "Bearer";
|
||||
|
||||
/// <summary>
|
||||
/// The password authentication header scheme
|
||||
/// </summary>
|
||||
const string passwordAuthenticationScheme = "Password";
|
||||
const string PasswordAuthenticationScheme = "Password";
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="AssemblyName"/>
|
||||
/// The current <see cref="System.Reflection.AssemblyName"/>
|
||||
/// </summary>
|
||||
static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of the <see cref="Api"/> the caller is using
|
||||
/// </summary>
|
||||
public static Version Version => assemblyName.Version;
|
||||
public static Version Version => AssemblyName.Version;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance.Id"/> being accessed
|
||||
@@ -139,12 +139,12 @@ namespace Tgstation.Server.Api
|
||||
if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out var clientUserAgent))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent));
|
||||
|
||||
//assure the client user agent has a name and version
|
||||
// assure the client user agent has a name and version
|
||||
if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out var clientVersion))
|
||||
throw new InvalidOperationException("Malformed client user agent!");
|
||||
|
||||
//make sure the api header matches ours
|
||||
if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name)
|
||||
// make sure the api header matches ours
|
||||
if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != AssemblyName.Name)
|
||||
throw new InvalidOperationException("Missing API version!");
|
||||
|
||||
if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion))
|
||||
@@ -166,7 +166,7 @@ namespace Tgstation.Server.Api
|
||||
if (String.IsNullOrEmpty(parameter))
|
||||
throw new InvalidOperationException("Missing authentication parameter!");
|
||||
|
||||
if (requestHeaders.Headers.TryGetValue(instanceIdHeader, out var instanceIdValues))
|
||||
if (requestHeaders.Headers.TryGetValue(InstanceIdHeader, out var instanceIdValues))
|
||||
{
|
||||
var instanceIdString = instanceIdValues.FirstOrDefault();
|
||||
if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId))
|
||||
@@ -175,17 +175,18 @@ namespace Tgstation.Server.Api
|
||||
|
||||
switch (scheme)
|
||||
{
|
||||
case jwtAuthenticationScheme:
|
||||
case JwtAuthenticationScheme:
|
||||
Token = parameter;
|
||||
break;
|
||||
case passwordAuthenticationScheme:
|
||||
case PasswordAuthenticationScheme:
|
||||
Password = parameter;
|
||||
var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out var values);
|
||||
var fail = !requestHeaders.Headers.TryGetValue(UsernameHeader, out var values);
|
||||
if (!fail)
|
||||
{
|
||||
Username = values.FirstOrDefault();
|
||||
fail = String.IsNullOrWhiteSpace(Username);
|
||||
}
|
||||
|
||||
if (fail)
|
||||
throw new InvalidOperationException("Missing Username header!");
|
||||
break;
|
||||
@@ -231,17 +232,18 @@ namespace Tgstation.Server.Api
|
||||
headers.Clear();
|
||||
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
|
||||
if (IsTokenAuthentication)
|
||||
headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token);
|
||||
headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
|
||||
else
|
||||
{
|
||||
headers.Authorization = new AuthenticationHeaderValue(passwordAuthenticationScheme, Password);
|
||||
headers.Add(usernameHeader, Username);
|
||||
headers.Authorization = new AuthenticationHeaderValue(PasswordAuthenticationScheme, Password);
|
||||
headers.Add(UsernameHeader, Username);
|
||||
}
|
||||
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
|
||||
headers.Add(ApiVersionHeader, new ProductHeaderValue(assemblyName.Name, ApiVersion.ToString()).ToString());
|
||||
headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
|
||||
instanceId = instanceId ?? InstanceId;
|
||||
if (instanceId.HasValue)
|
||||
headers.Add(instanceIdHeader, instanceId.ToString());
|
||||
headers.Add(InstanceIdHeader, instanceId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents administrative server information
|
||||
/// </summary>
|
||||
public sealed class Administration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
/// Internet relay chat
|
||||
/// </summary>
|
||||
Irc,
|
||||
|
||||
/// <summary>
|
||||
/// Superior chat service
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
/// <summary>
|
||||
/// The content of the <see cref="ConfigurationFile"/>. Will be <see langword="null"/> if <see cref="AccessDenied"/> is <see langword="true"/> or during listing and write operations
|
||||
/// </summary>
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
public byte[] Content { get; set; }
|
||||
#pragma warning restore CA1819 // Properties should not return arrays
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
/// Configuration editing is not allowed
|
||||
/// </summary>
|
||||
Disallowed,
|
||||
|
||||
/// <summary>
|
||||
/// Configuration editing is allowed by all users on all files
|
||||
/// </summary>
|
||||
HostWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Co
|
||||
/// </summary>
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
/// Server is unrestricted in terms of file access and shell commands
|
||||
/// </summary>
|
||||
Trusted,
|
||||
|
||||
/// <summary>
|
||||
/// Server will not be able to run shell commands or access files outside it's working directory
|
||||
/// </summary>
|
||||
Safe,
|
||||
|
||||
/// <summary>
|
||||
/// Server will not be able to run shell commands or access anything but temporary files. Currently unsupported!
|
||||
/// </summary>
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
/// Server is not running
|
||||
/// </summary>
|
||||
Offline,
|
||||
|
||||
/// <summary>
|
||||
/// Server is being rebooted
|
||||
/// </summary>
|
||||
HardRebooting,
|
||||
|
||||
/// <summary>
|
||||
/// Server is running
|
||||
/// </summary>
|
||||
|
||||
@@ -50,7 +50,10 @@ namespace Tgstation.Server.Api.Models
|
||||
[NotMapped]
|
||||
public Job MoveJob { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create a clone of the essential <see cref="Instance"/> metadata
|
||||
/// </summary>
|
||||
/// <returns>A clone of the essential <see cref="Instance"/> metadata</returns>
|
||||
public Instance CloneMetadata() => new Instance
|
||||
{
|
||||
Id = Id,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ChatBot.ConnectionString"/> associated with the <see cref="ChatConnectionStringBuilder"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>The <see cref="ChatBot.ConnectionString"/> associated with the <see cref="ChatConnectionStringBuilder"/></returns>
|
||||
public abstract override string ToString();
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,15 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// The revision sha
|
||||
/// </summary>
|
||||
[Required, StringLength(40)]
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string CommitSha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sha of the most recent remote commit
|
||||
/// </summary>
|
||||
[Required, StringLength(40)]
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string OriginCommitSha { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TestMergeBase"/> from a <paramref name="copy"/>
|
||||
/// </summary>
|
||||
/// <param name="copy"></param>
|
||||
/// <param name="copy">The <see cref="TestMergeBase"/> to copy data from</param>
|
||||
protected TestMergeBase(TestMergeBase copy)
|
||||
{
|
||||
if (copy == null)
|
||||
|
||||
@@ -87,6 +87,8 @@ namespace Tgstation.Server.Api.Models
|
||||
case IrcPasswordType.Server:
|
||||
PasswordType = passwordType;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (splits.Length < 6)
|
||||
@@ -116,6 +118,7 @@ namespace Tgstation.Server.Api.Models
|
||||
sb.Append(';');
|
||||
sb.Append(Password);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
/// Use server authentication
|
||||
/// </summary>
|
||||
Server,
|
||||
|
||||
/// <summary>
|
||||
/// Use PLAIN sasl authentication
|
||||
/// </summary>
|
||||
Sasl,
|
||||
|
||||
/// <summary>
|
||||
/// Use NickServ authentication
|
||||
/// </summary>
|
||||
|
||||
@@ -12,22 +12,27 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can edit themself and other <see cref="Models.User"/>s and also create others
|
||||
/// </summary>
|
||||
WriteUsers = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User can gracefully restart the host
|
||||
/// </summary>
|
||||
RestartHost = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Administration.NewVersion"/>
|
||||
/// </summary>
|
||||
ChangeVersion = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User can change their password
|
||||
/// </summary>
|
||||
EditOwnPassword = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User can read info and rights of other users
|
||||
/// </summary>
|
||||
|
||||
@@ -12,18 +12,22 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User may check the active installed BYOND version
|
||||
/// </summary>
|
||||
ReadActive = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User may list all installed BYOND versions
|
||||
/// </summary>
|
||||
ListInstalled = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User may change the active BYOND version
|
||||
/// </summary>
|
||||
ChangeVersion = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User may cancel version installations
|
||||
/// </summary>
|
||||
|
||||
@@ -12,38 +12,47 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Enabled"/>
|
||||
/// </summary>
|
||||
WriteEnabled = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Provider"/>
|
||||
/// </summary>
|
||||
WriteProvider = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.ChatBot.Channels"/>
|
||||
/// </summary>
|
||||
WriteChannels = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.ConnectionString"/>
|
||||
/// </summary>
|
||||
WriteConnectionString = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User can read <see cref="Models.Internal.ChatBot.ConnectionString"/> requires <see cref="Read"/>
|
||||
/// </summary>
|
||||
ReadConnectionString = 16,
|
||||
|
||||
/// <summary>
|
||||
/// User can read all chat settings except <see cref="Models.Internal.ChatBot.ConnectionString"/>
|
||||
/// </summary>
|
||||
Read = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User can create new <see cref="Models.ChatBot"/>
|
||||
/// </summary>
|
||||
Create = 64,
|
||||
|
||||
/// <summary>
|
||||
/// User can delete <see cref="Models.ChatBot"/>
|
||||
/// </summary>
|
||||
Delete = 128,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Name"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -12,18 +12,22 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User may read files
|
||||
/// </summary>
|
||||
Read = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User may write files
|
||||
/// </summary>
|
||||
Write = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User may list files
|
||||
/// </summary>
|
||||
List = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User may delete empty folders
|
||||
/// </summary>
|
||||
|
||||
@@ -12,50 +12,62 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can read <see cref="Models.DreamDaemon.ActiveCompileJob"/> and <see cref="Models.DreamDaemon.StagedCompileJob"/>
|
||||
/// </summary>
|
||||
ReadRevision = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User can change both primary and secondary ports
|
||||
/// </summary>
|
||||
SetPorts = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonSettings.AutoStart"/>
|
||||
/// </summary>
|
||||
SetAutoStart = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User set <see cref="Models.Internal.DreamDaemonLaunchParameters.SecurityLevel"/>
|
||||
/// </summary>
|
||||
SetSecurity = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User can read all ports, <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/>, <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>, <see cref="Models.DreamDaemon.Running"/>, <see cref="Models.Internal.DreamDaemonLaunchParameters.AllowWebClient"/>, and <see cref="Models.Internal.DreamDaemonSettings.AutoStart"/>
|
||||
/// </summary>
|
||||
ReadMetadata = 16,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.AllowWebClient"/>
|
||||
/// </summary>
|
||||
SetWebClient = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User can enable <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/>
|
||||
/// </summary>
|
||||
SoftRestart = 64,
|
||||
|
||||
/// <summary>
|
||||
/// User can enable <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>
|
||||
/// </summary>
|
||||
SoftShutdown = 128,
|
||||
|
||||
/// <summary>
|
||||
/// User can immediately restart <see cref="Models.DreamDaemon"/>
|
||||
/// </summary>
|
||||
Restart = 256,
|
||||
|
||||
/// <summary>
|
||||
/// User can immediately shutdown <see cref="Models.DreamDaemon"/>
|
||||
/// </summary>
|
||||
Shutdown = 512,
|
||||
|
||||
/// <summary>
|
||||
/// User can start <see cref="Models.DreamDaemon"/> and disable <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/> and <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>
|
||||
/// </summary>
|
||||
Start = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.StartupTimeout"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -12,30 +12,37 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User may read <see cref="Models.DreamMaker"/> status
|
||||
/// </summary>
|
||||
Read = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User may trigger compiles
|
||||
/// </summary>
|
||||
Compile = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User may cancel compiles
|
||||
/// </summary>
|
||||
CancelCompile = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.DreamMaker.ProjectName"/>
|
||||
/// </summary>
|
||||
SetDme = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.DreamMaker.ApiValidationPort"/>
|
||||
/// </summary>
|
||||
SetApiValidationPort = 16,
|
||||
|
||||
/// <summary>
|
||||
/// User may list and read all <see cref="Models.CompileJob"/>s
|
||||
/// </summary>
|
||||
CompileJobs = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.DreamMaker.ApiValidationSecurityLevel"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -12,38 +12,47 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can view <see cref="Models.Instance"/>s which they have any rights for
|
||||
/// </summary>
|
||||
Read = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User can create <see cref="Models.Instance"/>s
|
||||
/// </summary>
|
||||
Create = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can rename <see cref="Models.Instance"/>s they can view
|
||||
/// </summary>
|
||||
Rename = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User can relocate <see cref="Models.Instance"/>s they can view
|
||||
/// </summary>
|
||||
Relocate = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User can online <see cref="Models.Instance"/>s they can view
|
||||
/// </summary>
|
||||
SetOnline = 16,
|
||||
|
||||
/// <summary>
|
||||
/// User can delete <see cref="Models.Instance"/>s they can view
|
||||
/// </summary>
|
||||
Delete = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User can view all <see cref="Models.Instance"/>s
|
||||
/// </summary>
|
||||
List = 64,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.ConfigurationType"/>
|
||||
/// </summary>
|
||||
SetConfiguration = 128,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.AutoUpdateInterval"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -12,14 +12,17 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Allow read access to <see cref="Models.InstanceUser"/> for the <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
ReadUsers = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Allow write and delete access to <see cref="Models.InstanceUser"/> for the <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
WriteUsers = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allow adding additional <see cref="Models.InstanceUser"/> to the <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -12,54 +12,67 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User may cancel synchronize operations
|
||||
/// </summary>
|
||||
CancelPendingChanges = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User may create the <see cref="Models.Repository"/> if it does not exist
|
||||
/// </summary>
|
||||
SetOrigin = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User may directly set the sha the <see cref="Models.Repository"/>'s HEAD points to
|
||||
/// </summary>
|
||||
SetSha = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User may fetch and merge GitHub pull requests
|
||||
/// </summary>
|
||||
MergePullRequest = 8,
|
||||
|
||||
/// <summary>
|
||||
/// User may use the update feature
|
||||
/// </summary>
|
||||
UpdateBranch = 16,
|
||||
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.CommitterName"/> and <see cref="Models.Internal.RepositorySettings.CommitterEmail"/>
|
||||
/// </summary>
|
||||
ChangeCommitter = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.PushTestMergeCommits"/> and <see cref="Models.Internal.RepositorySettings.PostTestMergeComment"/>
|
||||
/// </summary>
|
||||
ChangeTestMergeCommits = 64,
|
||||
|
||||
/// <summary>
|
||||
/// User may read and change <see cref="Models.Internal.RepositorySettings.AccessUser"/> and <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
ChangeCredentials = 128,
|
||||
|
||||
/// <summary>
|
||||
/// User may set <see cref="Models.Repository.Reference"/> to another git reference (not a SHA)
|
||||
/// </summary>
|
||||
SetReference = 256,
|
||||
|
||||
/// <summary>
|
||||
/// User may read all fields in the <see cref="Models.Repository"/> with the exception of <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
Read = 512,
|
||||
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.AutoUpdatesKeepTestMerges"/> and <see cref="Models.Internal.RepositorySettings.AutoUpdatesSynchronize"/>
|
||||
/// </summary>
|
||||
ChangeAutoUpdateSettings = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// User may delete the <see cref="Models.Repository"/>
|
||||
/// </summary>
|
||||
Delete = 2048,
|
||||
|
||||
/// <summary>
|
||||
/// User may cancel clone operations
|
||||
/// </summary>
|
||||
|
||||
@@ -12,11 +12,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <summary>
|
||||
/// Map of <see cref="RightsType"/>s to their respective flag <see cref="Enum"/>s
|
||||
/// </summary>
|
||||
static readonly IReadOnlyDictionary<RightsType, Type> typeMap = new Dictionary<RightsType, Type>
|
||||
static readonly IReadOnlyDictionary<RightsType, Type> TypeMap = new Dictionary<RightsType, Type>
|
||||
{
|
||||
{ RightsType.Administration, typeof(AdministrationRights) },
|
||||
{ RightsType.InstanceManager, typeof(InstanceManagerRights) },
|
||||
|
||||
{ RightsType.Repository, typeof(RepositoryRights) },
|
||||
{ RightsType.Byond, typeof(ByondRights) },
|
||||
{ RightsType.DreamMaker, typeof(DreamMakerRights) },
|
||||
@@ -31,7 +30,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
/// <param name="rightsType">The <see cref="RightsType"/> to lookup</param>
|
||||
/// <returns>The <see cref="Enum"/> <see cref="Type"/> of the given <paramref name="rightsType"/></returns>
|
||||
public static Type RightToType(RightsType rightsType) => typeMap[rightsType];
|
||||
public static Type RightToType(RightsType rightsType) => TypeMap[rightsType];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role claim name used for a given <paramref name="right"/>
|
||||
@@ -47,7 +46,8 @@ namespace Tgstation.Server.Api.Rights
|
||||
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());
|
||||
};
|
||||
}
|
||||
|
||||
var names = GetRoleNames();
|
||||
return String.Join(",", names);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <returns>A <see cref="string"/> representing the claim role name</returns>
|
||||
public static string RoleName(RightsType rightsType, Enum right)
|
||||
{
|
||||
var enumType = typeMap[rightsType];
|
||||
var enumType = TypeMap[rightsType];
|
||||
return String.Concat(enumType.Name, '.', Enum.GetName(enumType, right));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,34 +9,42 @@
|
||||
/// <see cref="AdministrationRights"/>
|
||||
/// </summary>
|
||||
Administration,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="InstanceManagerRights"/>
|
||||
/// </summary>
|
||||
InstanceManager,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RepositoryRights"/>
|
||||
/// </summary>
|
||||
Repository,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ByondRights"/>
|
||||
/// </summary>
|
||||
Byond,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DreamMakerRights"/>
|
||||
/// </summary>
|
||||
DreamMaker,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DreamDaemonRights"/>
|
||||
/// </summary>
|
||||
DreamDaemon,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ChatBotRights"/>
|
||||
/// </summary>
|
||||
ChatBots,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ConfigurationRights"/>
|
||||
/// </summary>
|
||||
Configuration,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="InstanceUserRights"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
<PackageTags>json web api tgstation-server tgstation ss13 byond</PackageTags>
|
||||
<PackageReleaseNotes>Initial release</PackageReleaseNotes>
|
||||
<Version>4.0.1.0</Version>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.0\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819;CA1028</NoWarn>
|
||||
<NoWarn>1701;1702;CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819;CA1028</NoWarn>
|
||||
<NoWarn>1701;1702;CA1028</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netstandard2.0\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -37,7 +38,14 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -49,6 +49,79 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
ApiHeaders headers;
|
||||
|
||||
static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Converters = new[] { new VersionConverter() }
|
||||
};
|
||||
|
||||
static void HandleBadResponse(HttpResponseMessage response, string json)
|
||||
{
|
||||
ErrorMessage errorMessage = null;
|
||||
try
|
||||
{
|
||||
// check if json serializes to an error message
|
||||
errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, GetSerializerSettings());
|
||||
}
|
||||
catch (JsonException) { }
|
||||
|
||||
const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!";
|
||||
|
||||
#pragma warning disable IDE0010 // Add missing cases
|
||||
switch (response.StatusCode)
|
||||
#pragma warning restore IDE0010 // Add missing cases
|
||||
{
|
||||
case HttpStatusCode.UpgradeRequired:
|
||||
throw new ApiMismatchException(errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "API Mismatch but no current API version provided!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
});
|
||||
case HttpStatusCode.Unauthorized:
|
||||
throw new UnauthorizedException();
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
throw new RequestTimeoutException();
|
||||
case HttpStatusCode.Forbidden:
|
||||
throw new InsufficientPermissionsException();
|
||||
case HttpStatusCode.ServiceUnavailable:
|
||||
throw new ServiceUnavailableException();
|
||||
case HttpStatusCode.Gone:
|
||||
errorMessage = errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "The requested resource could not be found!",
|
||||
SeverApiVersion = null
|
||||
};
|
||||
goto case HttpStatusCode.Conflict;
|
||||
case HttpStatusCode.NotFound:
|
||||
// our fault somehow
|
||||
errorMessage = errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "This is not a valid route!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
};
|
||||
goto case HttpStatusCode.Conflict;
|
||||
case HttpStatusCode.Conflict:
|
||||
throw new ConflictException(errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "An undescribed conflict occurred!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
}, response.StatusCode);
|
||||
case HttpStatusCode.NotImplemented:
|
||||
// unprocessable entity
|
||||
case (HttpStatusCode)422:
|
||||
throw new MethodNotSupportedException();
|
||||
case HttpStatusCode.InternalServerError:
|
||||
// response json is html
|
||||
throw new ServerErrorException(json);
|
||||
case (HttpStatusCode)429:
|
||||
// rate limited
|
||||
response.Headers.TryGetValues("Retry-After", out var values);
|
||||
throw new RateLimitException(values?.FirstOrDefault());
|
||||
default:
|
||||
throw new ApiConflictException(errorMessage, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiClient"/>
|
||||
/// </summary>
|
||||
@@ -60,7 +133,7 @@ namespace Tgstation.Server.Client
|
||||
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
Url = url ?? throw new ArgumentNullException(nameof(url));
|
||||
headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
|
||||
|
||||
|
||||
requestLoggers = new List<IRequestLogger>();
|
||||
}
|
||||
|
||||
@@ -70,10 +143,11 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Main request method
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The resulting POCO type</typeparam>
|
||||
/// <param name="route">The route to run</param>
|
||||
/// <param name="body">The body of the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="instanceId">The optional <see cref="Api.Models.Instance.Id"/> for the request</param>
|
||||
/// <param name="instanceId">The optional <see cref="Instance.Id"/> for the request</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
async Task<TResult> RunRequest<TResult>(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
|
||||
@@ -85,102 +159,42 @@ namespace Tgstation.Server.Client
|
||||
if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
|
||||
throw new InvalidOperationException("Body cannot be null for POST or PUT!");
|
||||
|
||||
HttpResponseMessage response;
|
||||
var fullUri = new Uri(Url, route);
|
||||
|
||||
var message = new HttpRequestMessage(method, fullUri);
|
||||
|
||||
var serializerSettings = new JsonSerializerSettings
|
||||
var serializerSettings = GetSerializerSettings();
|
||||
using (var request = new HttpRequestMessage(method, fullUri))
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Converters = new[] { new VersionConverter() }
|
||||
};
|
||||
if (body != null)
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
|
||||
|
||||
if (body != null)
|
||||
message.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
|
||||
headers.SetRequestHeaders(request.Headers, instanceId);
|
||||
|
||||
headers.SetRequestHeaders(message.Headers, instanceId);
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(message, cancellationToken))).ConfigureAwait(false);
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var response = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
using (response)
|
||||
{
|
||||
ErrorMessage errorMessage = null;
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
HandleBadResponse(response, json);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(json))
|
||||
json = JsonConvert.SerializeObject(new object());
|
||||
|
||||
try
|
||||
{
|
||||
//check if json serializes to an error message
|
||||
errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, serializerSettings);
|
||||
return JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
|
||||
}
|
||||
catch (JsonException) { }
|
||||
|
||||
const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!";
|
||||
|
||||
switch (response.StatusCode)
|
||||
catch (JsonException)
|
||||
{
|
||||
case HttpStatusCode.UpgradeRequired:
|
||||
throw new ApiMismatchException(errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "API Mismatch but no current API version provided!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
});
|
||||
case HttpStatusCode.Unauthorized:
|
||||
throw new UnauthorizedException();
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
throw new RequestTimeoutException();
|
||||
case HttpStatusCode.Forbidden:
|
||||
throw new InsufficientPermissionsException();
|
||||
case HttpStatusCode.ServiceUnavailable:
|
||||
throw new ServiceUnavailableException();
|
||||
case HttpStatusCode.Gone:
|
||||
errorMessage = errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "The requested resource could not be found!",
|
||||
SeverApiVersion = null
|
||||
};
|
||||
goto case HttpStatusCode.Conflict;
|
||||
case HttpStatusCode.NotFound: //our fault somehow
|
||||
errorMessage = errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "This is not a valid route!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
};
|
||||
goto case HttpStatusCode.Conflict;
|
||||
case HttpStatusCode.Conflict:
|
||||
throw new ConflictException(errorMessage ?? new ErrorMessage
|
||||
{
|
||||
Message = "An undescribed conflict occurred!" + BadSpecExtension,
|
||||
SeverApiVersion = null
|
||||
}, response.StatusCode);
|
||||
case HttpStatusCode.NotImplemented:
|
||||
case (HttpStatusCode)422: //unprocessable entity
|
||||
throw new MethodNotSupportedException();
|
||||
case HttpStatusCode.InternalServerError:
|
||||
//response
|
||||
throw new ServerErrorException(json); //json is html
|
||||
case (HttpStatusCode)429: //rate limited
|
||||
response.Headers.TryGetValues("Retry-After", out var values);
|
||||
throw new RateLimitException(values?.FirstOrDefault());
|
||||
default:
|
||||
throw new ApiConflictException(errorMessage, response.StatusCode);
|
||||
throw new UnrecognizedResponseException(json, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(json))
|
||||
json = JsonConvert.SerializeObject(new object());
|
||||
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
throw new UnrecognizedResponseException(json, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="DreamDaemonClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="DreamDaemonClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
@@ -33,6 +34,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// The <see cref="IApiClient"/> for the <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
@@ -22,7 +23,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// Construct a <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public RepositoryClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
|
||||
@@ -10,10 +10,19 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
interface IApiClient : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ApiHeaders"/> the <see cref="IApiClient"/> uses
|
||||
/// </summary>
|
||||
ApiHeaders Headers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Uri"/> pointing the tgstation-server
|
||||
/// </summary>
|
||||
Uri Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The request timeout
|
||||
/// </summary>
|
||||
TimeSpan Timeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -22,21 +31,155 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="requestLogger">The <see cref="IRequestLogger"/> to add</param>
|
||||
void AddRequestLogger(IRequestLogger requestLogger);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP GET request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP POST request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP POST request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP POST request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(string route, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PATCH request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP GET request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP POST request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="ServerInformation"/> of the <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerInformation"/> of the target server</returns>
|
||||
Task<ServerInformation> Version(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="host">The URL to access TGS</param>
|
||||
/// <param name="username">The username to for the <see cref="IServerClient"/></param>
|
||||
/// <param name="password">The password for the <see cref="IServerClient"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="timeout">The <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/></returns>
|
||||
Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceManagerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient"></param>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
public InstanceManagerClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
@@ -55,6 +55,7 @@ namespace Tgstation.Server.Client
|
||||
client = new InstanceClient(apiClient, instance);
|
||||
cachedClients.Add(instance.Id, client);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClientFactory"/> for the <see cref="ServerClientFactory"/>
|
||||
/// </summary>
|
||||
static readonly IApiClientFactory apiClientFactory = new ApiClientFactory();
|
||||
static readonly IApiClientFactory ApiClientFactory = new ApiClientFactory();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ProductHeaderValue"/> for the <see cref="ServerClientFactory"/>
|
||||
@@ -40,12 +40,13 @@ namespace Tgstation.Server.Client
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
|
||||
Token token;
|
||||
using (var api = apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
|
||||
using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
|
||||
{
|
||||
if (timeout != default)
|
||||
api.Timeout = timeout;
|
||||
token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return CreateServerClient(host, token, timeout);
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ namespace Tgstation.Server.Client
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
var result = new ServerClient(apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
|
||||
var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
|
||||
if (timeout != default)
|
||||
result.Timeout = timeout;
|
||||
return result;
|
||||
|
||||
@@ -18,18 +18,20 @@
|
||||
<PackageTags>json web api tgstation-server tgstation ss13 byond client</PackageTags>
|
||||
<PackageReleaseNotes>Added the ability to change the Token of IServerClient</PackageReleaseNotes>
|
||||
<Copyright>2018</Copyright>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.0\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netstandard2.0\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -38,10 +40,17 @@
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Tgstation.Server.Host.Console
|
||||
loggerFactory.CreateLogger(nameof(Program)).LogCritical("Please specify only 1 of --trace-host-watchdog or --debug-host-watchdog!");
|
||||
return;
|
||||
}
|
||||
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
void AppDomainHandler(object a, EventArgs b) => cts.Cancel();
|
||||
|
||||
@@ -4,18 +4,20 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<DebugType>Full</DebugType>
|
||||
<Version>4.0.1.3</Version>
|
||||
<Version>4.0.1.4</Version>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netcoreapp2.1\Tgstation.Server.Host.Console.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702;SA1652</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netcoreapp2.1\Tgstation.Server.Host.Console.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,6 +26,10 @@
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -39,4 +45,7 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
@@ -20,6 +20,13 @@ namespace Tgstation.Server.Host.Service
|
||||
/// </summary>
|
||||
sealed class Program
|
||||
{
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdogFactory"/> for the <see cref="Program"/>
|
||||
/// </summary>
|
||||
internal static IWatchdogFactory WatchdogFactory = new WatchdogFactory();
|
||||
#pragma warning restore SA1401 // Fields should be private
|
||||
|
||||
/// <summary>
|
||||
/// The --uninstall or -u option
|
||||
/// </summary>
|
||||
@@ -50,8 +57,6 @@ namespace Tgstation.Server.Host.Service
|
||||
[Option(ShortName = "d")]
|
||||
public bool Debug { get; set; }
|
||||
|
||||
static readonly IWatchdogFactory watchdogFactory = new WatchdogFactory();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the running user is a system administrator
|
||||
/// </summary>
|
||||
@@ -63,9 +68,15 @@ namespace Tgstation.Server.Host.Service
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint for the application
|
||||
/// </summary>
|
||||
static Task<int> Main(string[] args) => CommandLineApplication.ExecuteAsync<Program>(args);
|
||||
|
||||
/// <summary>
|
||||
/// Command line handler, always runs
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
public async Task OnExecuteAsync()
|
||||
{
|
||||
if (Environment.UserInteractive)
|
||||
@@ -81,8 +92,8 @@ namespace Tgstation.Server.Host.Service
|
||||
|
||||
if (!IsAdministrator())
|
||||
{
|
||||
//try to restart as admin
|
||||
//its windows, first arg is .exe name guaranteed
|
||||
// try to restart as admin
|
||||
// its windows, first arg is .exe name guaranteed
|
||||
var exe = Environment.GetCommandLineArgs().First();
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
@@ -100,13 +111,12 @@ namespace Tgstation.Server.Host.Service
|
||||
using (var loggerFactory = new LoggerFactory())
|
||||
{
|
||||
if (Configure)
|
||||
await watchdogFactory.CreateWatchdog(loggerFactory).RunAsync(true, Array.Empty<string>(), default).ConfigureAwait(false);
|
||||
await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(true, Array.Empty<string>(), default).ConfigureAwait(false);
|
||||
|
||||
if (Install)
|
||||
{
|
||||
if (Uninstall)
|
||||
//oh no, it's retarded...
|
||||
return;
|
||||
return; // oh no, it's retarded...
|
||||
using (var processInstaller = new ServiceProcessInstaller())
|
||||
using (var installer = new ServiceInstaller())
|
||||
{
|
||||
@@ -132,14 +142,9 @@ namespace Tgstation.Server.Host.Service
|
||||
installer.ServiceName = ServerService.Name;
|
||||
installer.Uninstall(null);
|
||||
}
|
||||
else if(!Configure)
|
||||
ServiceBase.Run(new ServerService(watchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information));
|
||||
else if (!Configure)
|
||||
ServiceBase.Run(new ServerService(WatchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint for the application
|
||||
/// </summary>
|
||||
static Task<int> Main(string[] args) => CommandLineApplication.ExecuteAsync<Program>(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ using System.Runtime.InteropServices;
|
||||
[assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")]
|
||||
[assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")]
|
||||
|
||||
[assembly: AssemblyVersion("4.0.1.3")]
|
||||
[assembly: AssemblyFileVersion("4.0.1.3")]
|
||||
[assembly: AssemblyVersion("4.0.1.4")]
|
||||
[assembly: AssemblyFileVersion("4.0.1.4")]
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Service
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunWatchdog(string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
await watchdog.RunAsync(false, args, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
await watchdog.RunAsync(false, args, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
void StopServiceAsync()
|
||||
{
|
||||
@@ -88,11 +88,12 @@ namespace Tgstation.Server.Host.Service
|
||||
EventLog.WriteEntry(String.Format(CultureInfo.InvariantCulture, "Error stopping service! Exception: {0}", e));
|
||||
}
|
||||
}
|
||||
|
||||
StopServiceAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource")]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource", Justification = "IT'S DISPOSED RIGHT THERE YOU FUCCBOI!")]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
cancellationTokenSource?.Dispose();
|
||||
|
||||
@@ -26,33 +26,33 @@
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>bin\Debug\Tgstation.Server.Host.Service.xml</DocumentationFile>
|
||||
<CodeAnalysisRuleSet />
|
||||
<NoWarn>SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Release\Tgstation.Server.Host.Service.xml</DocumentationFile>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet />
|
||||
<NoWarn>SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Tgstation.Server.Host.Service.Program</StartupObject>
|
||||
@@ -104,6 +104,15 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog">
|
||||
<Version>2.1.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\build\stylecop.json">
|
||||
<Link>stylecop.json</Link>
|
||||
</AdditionalFiles>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -4,18 +4,20 @@
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<DebugType>Full</DebugType>
|
||||
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
|
||||
<Version>4.0.1.3</Version>
|
||||
<Version>4.0.1.4</Version>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.0\Tgstation.Server.Host.Watchdog.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DocumentationFile>bin\Debug\netstandard2.0\Tgstation.Server.Host.Watchdog.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;SA1652</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,6 +26,10 @@
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -31,4 +37,8 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <inheritdoc />
|
||||
sealed class Watchdog : IWatchdog
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Watchdog"/>
|
||||
/// </summary>
|
||||
@@ -31,6 +30,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
public async Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Host watchdog starting...");
|
||||
@@ -67,17 +67,18 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Detected dotnet executable at {0}", dotnetPath);
|
||||
|
||||
var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog
|
||||
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); // always always next to watchdog
|
||||
#if DEBUG
|
||||
Directory.CreateDirectory(assemblyStoragePath);
|
||||
#endif
|
||||
var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default"));
|
||||
#if DEBUG
|
||||
//just copy the shit where it belongs
|
||||
// just copy the shit where it belongs
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
Directory.CreateDirectory(defaultAssemblyPath);
|
||||
|
||||
@@ -117,7 +118,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = dotnetPath;
|
||||
process.StartInfo.WorkingDirectory = rootLocation; //for appsettings
|
||||
process.StartInfo.WorkingDirectory = rootLocation; // for appsettings
|
||||
|
||||
var arguments = new List<string>
|
||||
{
|
||||
@@ -138,7 +139,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
process.StartInfo.Arguments = String.Join(" ", arguments);
|
||||
|
||||
process.StartInfo.UseShellExecute = false; //runs in the same console
|
||||
process.StartInfo.UseShellExecute = false; // runs in the same console
|
||||
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
process.Exited += (a, b) =>
|
||||
@@ -172,7 +173,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
processCts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (ObjectDisposedException) { } //race conditions
|
||||
catch (ObjectDisposedException) { } // race conditions
|
||||
}))
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -204,13 +205,12 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
return;
|
||||
case 1:
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
//just a restart
|
||||
logger.LogInformation("Watchdog will restart host...");
|
||||
logger.LogInformation("Watchdog will restart host..."); // just a restart
|
||||
else
|
||||
logger.LogWarning("Host requested restart but watchdog shutdown is in progress!");
|
||||
break;
|
||||
case 2:
|
||||
//update path is now an exception document
|
||||
// update path is now an exception document
|
||||
logger.LogCritical("Host crashed, propagating exception dump...");
|
||||
var data = File.ReadAllText(updateDirectory);
|
||||
try
|
||||
@@ -221,29 +221,36 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
logger.LogWarning("Unable to delete exception dump file at {0}! Exception: {1}", updateDirectory, e);
|
||||
}
|
||||
|
||||
#pragma warning disable CA2201 // Do not raise reserved exception types
|
||||
throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host propagated exception: {0}", data));
|
||||
#pragma warning restore CA2201 // Do not raise reserved exception types
|
||||
default:
|
||||
if (killedHostProcess)
|
||||
{
|
||||
logger.LogWarning("Watchdog forced to kill host process!");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
#pragma warning disable CA2201 // Do not raise reserved exception types
|
||||
throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host crashed with exit code {0}!", process.ExitCode));
|
||||
#pragma warning restore CA2201 // Do not raise reserved exception types
|
||||
}
|
||||
}
|
||||
|
||||
//HEY YOU
|
||||
//BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY
|
||||
//THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW
|
||||
// HEY YOU
|
||||
// BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY
|
||||
// THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW
|
||||
if (Directory.Exists(updateDirectory))
|
||||
{
|
||||
logger.LogInformation("Applying server update...");
|
||||
if (isWindows)
|
||||
{
|
||||
//windows dick sucking resource unlocking
|
||||
// windows dick sucking resource unlocking
|
||||
GC.Collect(Int32.MaxValue, GCCollectionMode.Default, true);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString());
|
||||
try
|
||||
{
|
||||
@@ -293,5 +300,6 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
logger.LogInformation("Host watchdog exiting...");
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
}
|
||||
}
|
||||
|
||||
+13833
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react-dom": "^16.5.2",
|
||||
"tgstation-server-control-panel": "^0.1.5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf build && react-scripts-ts build && cp-cli node_modules/tgstation-server-control-panel/build/public/ build/ && cp-cli ../../../build/tgs.ico build/favicon.ico && minify-json build && rimraf ../wwwroot && move-cli build ../wwwroot"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^10.12.0",
|
||||
"@types/react-dom": "^16.0.9",
|
||||
"cp-cli": "^1.1.2",
|
||||
"minify-json": "^1.0.0",
|
||||
"move-cli": "^1.2.0",
|
||||
"react-scripts-ts": "^3.1.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"typescript": "^3.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Tgstation Server Control Panel</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "TGS",
|
||||
"name": "Tgstation Server",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import ControlPanel from 'tgstation-server-control-panel';
|
||||
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const contentElement = document.getElementById('root') as HTMLElement;
|
||||
const legacyUserLanguageKey = 'userLanguage';
|
||||
const userLang = navigator.language || navigator[legacyUserLanguageKey];
|
||||
const serverAddress = window.location.href;
|
||||
const controlPanel = <ControlPanel serverAddress={serverAddress} locale={userLang} />;
|
||||
|
||||
registerServiceWorker();
|
||||
ReactDOM.render(controlPanel, contentElement);
|
||||
@@ -0,0 +1,124 @@
|
||||
// tslint:disable:no-console
|
||||
// tslint:disable:curly
|
||||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the 'N+1' visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL!,
|
||||
window.location.toString()
|
||||
);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://goo.gl/SC7cgQ'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl: string) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker) {
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a 'New content is
|
||||
// available; please refresh.' message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// 'Content is cached for offline use.' message.
|
||||
console.log('Content is cached for offline use.');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type')!.indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"importHelpers": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"scripts",
|
||||
"acceptance-tests",
|
||||
"webpack",
|
||||
"jest",
|
||||
"src/setupTests.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": [ "tslint:recommended", "tslint-react", "tslint-config-prettier" ],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"config/**/*.js",
|
||||
"node_modules/**/*.ts",
|
||||
"coverage/lcov-report/*.js"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"curly": [ true, "as-needed" ],
|
||||
"semicolon": [ true, "always" ]
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath));
|
||||
}
|
||||
|
||||
//at one point in design, byond versions were to delete themselves if they werent the active version
|
||||
//That changed at some point so these functions are intentioanlly left blank
|
||||
// at one point in design, byond versions were to delete themselves if they weren't the active version
|
||||
// That changed at some point so these functions are intentioanlly left blank
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// The file in which we store the <see cref="VersionKey(Version)"/> for installations
|
||||
/// </summary>
|
||||
const string VersionFileName = "Version.txt";
|
||||
|
||||
/// <summary>
|
||||
/// The file in which we store the <see cref="VersionKey(Version)"/> for the active installation
|
||||
/// </summary>
|
||||
@@ -119,6 +120,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (!installed)
|
||||
installedVersions.Add(versionKey, ourTcs.Task);
|
||||
}
|
||||
|
||||
if (installed)
|
||||
using (cancellationToken.Register(() => ourTcs.SetCanceled()))
|
||||
{
|
||||
@@ -126,7 +128,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return;
|
||||
}
|
||||
//okay up to us to install it then
|
||||
|
||||
// okay up to us to install it then
|
||||
try
|
||||
{
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List<string> { versionKey }, cancellationToken).ConfigureAwait(false);
|
||||
@@ -141,12 +144,12 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
await ioManager.ZipToDirectory(versionKey, download, cancellationToken).ConfigureAwait(false);
|
||||
await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//make sure to do this last because this is what tells us we have a valid version in the future
|
||||
// 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(version.ToString()), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
//since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
|
||||
// since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
|
||||
throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -205,9 +208,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
async Task<byte[]> GetActiveVersion()
|
||||
{
|
||||
if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false))
|
||||
return null;
|
||||
return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var activeVersionBytesTask = GetActiveVersion();
|
||||
@@ -221,9 +223,10 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); //cleanup
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); // cleanup
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false);
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
if (Version.TryParse(text, out var version))
|
||||
@@ -236,8 +239,9 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <param name="path">The path to the BYOND installation</param>
|
||||
/// <param name="version">The <see cref="Version"/> of BYOND being installed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task InstallByond(string path, Version version, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// </summary>
|
||||
/// <param name="version">The new <see cref="Version"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ChangeVersion(Version version, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Byond
|
||||
@@ -18,7 +16,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <summary>
|
||||
/// The URL format string for getting BYOND linux version {0}.{1} zipfile
|
||||
/// </summary>
|
||||
const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the BYOND cache
|
||||
/// </summary>
|
||||
@@ -85,7 +84,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor);
|
||||
|
||||
return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -98,8 +97,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
|
||||
//write the scripts for running the ting
|
||||
//need to add $ORIGIN to LD_LIBRARY_PATH
|
||||
// write the scripts for running the ting
|
||||
// need to add $ORIGIN to LD_LIBRARY_PATH
|
||||
const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n";
|
||||
|
||||
var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamDaemonExecutableName);
|
||||
|
||||
@@ -17,19 +17,23 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <summary>
|
||||
/// The URL format string for getting BYOND windows version {0}.{1} zipfile
|
||||
/// </summary>
|
||||
const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to byond installation configuration
|
||||
/// </summary>
|
||||
const string ByondConfigDir = "byond/cfg";
|
||||
|
||||
/// <summary>
|
||||
/// BYOND's DreamDaemon config file
|
||||
/// </summary>
|
||||
const string ByondDDConfig = "daemon.txt";
|
||||
|
||||
/// <summary>
|
||||
/// Setting to add to <see cref="ByondDDConfig"/> to suppress an invisible user prompt for running a trusted mode .dmb
|
||||
/// </summary>
|
||||
const string ByondNoPromptTrustedMode = "trusted-check 0";
|
||||
|
||||
/// <summary>
|
||||
/// The directory that contains the BYOND directx redistributable
|
||||
/// </summary>
|
||||
@@ -105,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <inheritdoc />
|
||||
public Task<byte[]> DownloadVersion(Version version, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor);
|
||||
|
||||
return ioManager.DownloadFile(new Uri(url), cancellationToken);
|
||||
}
|
||||
@@ -118,20 +122,21 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
var configPath = ioManager.ConcatPath(path, ByondConfigDir);
|
||||
await ioManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false);
|
||||
await ioManager.WriteAllBytes(ioManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
var setNoPromptTrustedModeTask = SetNoPromptTrusted();
|
||||
|
||||
//after this version lummox made DD depend of directx lol
|
||||
//but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/
|
||||
// after this version lummox made DD depend of directx lol
|
||||
// but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/
|
||||
if (version.Major == 512 && version.Minor >= 1427 && version.Minor < 1452 && !installedDirectX)
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
//check again because race conditions
|
||||
if (!installedDirectX)
|
||||
{
|
||||
//always install it, it's pretty fast and will do better redundancy checking than us
|
||||
// ^check again because race conditions
|
||||
// always install it, it's pretty fast and will do better redundancy checking than us
|
||||
var rbdx = ioManager.ConcatPath(path, ByondDXDir);
|
||||
//noShellExecute because we aren't doing runas shennanigans
|
||||
|
||||
// noShellExecute because we aren't doing runas shennanigans
|
||||
IProcess directXInstaller;
|
||||
try
|
||||
{
|
||||
@@ -141,6 +146,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e);
|
||||
}
|
||||
|
||||
using (directXInstaller)
|
||||
{
|
||||
int exitCode;
|
||||
|
||||
@@ -38,12 +38,12 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// If this is considered a channel for admin commands
|
||||
/// </summary>
|
||||
public bool IsAdmin { get; set; }
|
||||
public bool IsAdminChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this is a 1-to-1 chat channel
|
||||
/// </summary>
|
||||
public bool IsPrivate { get; set; }
|
||||
public bool IsPrivateChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For user use
|
||||
|
||||
@@ -15,6 +15,8 @@ using Tgstation.Server.Host.IO;
|
||||
namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
/// <inheritdoc />
|
||||
// TODO: Decomplexify
|
||||
#pragma warning disable CA1506
|
||||
sealed class Chat : IChat, IRestartHandler
|
||||
{
|
||||
const string CommonMention = "!tgs";
|
||||
@@ -177,6 +179,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
else
|
||||
task = Task.CompletedTask;
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
return provider;
|
||||
}
|
||||
@@ -190,12 +193,12 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
//map the channel if it's private and we haven't seen it
|
||||
// map the channel if it's private and we haven't seen it
|
||||
lock (providers)
|
||||
{
|
||||
var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
|
||||
var enumerable = mappedChannels.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == message.User.Channel.RealId);
|
||||
if (message.User.Channel.IsPrivate)
|
||||
if (message.User.Channel.IsPrivateChannel)
|
||||
lock (mappedChannels)
|
||||
{
|
||||
if (!provider.Connected)
|
||||
@@ -219,11 +222,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
else
|
||||
{
|
||||
//need to add tag and isAdminChannel
|
||||
// need to add tag and isAdminChannel
|
||||
var mapping = enumerable.First().Value;
|
||||
message.User.Channel.Id = mapping.Channel.Id;
|
||||
message.User.Channel.Tag = mapping.Channel.Tag;
|
||||
message.User.Channel.IsAdmin = mapping.Channel.IsAdmin;
|
||||
message.User.Channel.IsAdminChannel = mapping.Channel.IsAdminChannel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +239,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant();
|
||||
|
||||
if (!addressed && !message.User.Channel.IsPrivate)
|
||||
//no mention
|
||||
// no mention
|
||||
if (!addressed && !message.User.Channel.IsPrivateChannel)
|
||||
return;
|
||||
|
||||
logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User));
|
||||
@@ -247,7 +250,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
if (splits.Count == 0)
|
||||
{
|
||||
//just a mention
|
||||
// just a mention
|
||||
await SendMessage("Hi!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -266,8 +269,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
handler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == commandName).FirstOrDefault();
|
||||
}
|
||||
|
||||
return handler;
|
||||
};
|
||||
}
|
||||
|
||||
const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands.";
|
||||
|
||||
@@ -290,6 +294,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
else
|
||||
helpText = UnknownCommandMessage;
|
||||
}
|
||||
|
||||
await SendMessage(helpText, new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -302,7 +307,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandHandler.AdminOnly && !message.User.Channel.IsAdmin)
|
||||
if (commandHandler.AdminOnly && !message.User.Channel.IsAdminChannel)
|
||||
{
|
||||
await SendMessage("Use this command in an admin channel!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
@@ -318,7 +323,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//error bc custom commands should reply about why it failed
|
||||
// error bc custom commands should reply about why it failed
|
||||
logger.LogError("Error processing chat command: {0}", e);
|
||||
await SendMessage("Internal error processing command!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -336,11 +341,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//prune disconnected providers
|
||||
// prune disconnected providers
|
||||
foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList())
|
||||
messageTasks.Remove(I.Key);
|
||||
|
||||
//add new ones
|
||||
// add new ones
|
||||
Task updatedTask;
|
||||
lock (this)
|
||||
updatedTask = connectionsUpdated.Task;
|
||||
@@ -355,10 +360,10 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
continue;
|
||||
}
|
||||
|
||||
//wait for a message
|
||||
// wait for a message
|
||||
await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false);
|
||||
|
||||
//process completed ones
|
||||
// process completed ones
|
||||
foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList())
|
||||
{
|
||||
var message = await I.Value.ConfigureAwait(false);
|
||||
@@ -385,7 +390,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (provider == null)
|
||||
return;
|
||||
var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false);
|
||||
if (results == null) //aborted
|
||||
if (results == null) // aborted
|
||||
return;
|
||||
var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping
|
||||
{
|
||||
@@ -407,7 +412,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (mappedChannels)
|
||||
{
|
||||
lock (providers)
|
||||
if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again
|
||||
if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again
|
||||
return;
|
||||
foreach (var I in mappings)
|
||||
{
|
||||
@@ -419,6 +424,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (trackingContexts)
|
||||
task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken)));
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -444,7 +450,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
Task disconnectTask;
|
||||
lock (providers)
|
||||
{
|
||||
//raw settings changes forces a rebuild of the provider
|
||||
// raw settings changes forces a rebuild of the provider
|
||||
if (providers.TryGetValue(newSettings.Id, out provider))
|
||||
{
|
||||
providers.Remove(newSettings.Id);
|
||||
@@ -471,7 +477,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
await provider.Connect(cancellationToken).ConfigureAwait(false);
|
||||
lock (this)
|
||||
{
|
||||
//same thread shennanigans
|
||||
// same thread shennanigans
|
||||
var oldOne = connectionsUpdated;
|
||||
connectionsUpdated = new TaskCompletionSource<object>();
|
||||
oldOne.SetResult(null);
|
||||
@@ -506,7 +512,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
List<ulong> wdChannels;
|
||||
message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
@@ -516,7 +522,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
List<ulong> wdChannels;
|
||||
message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message);
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
@@ -547,11 +553,17 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (customCommandHandler == null)
|
||||
throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!");
|
||||
JsonTrackingContext context = null;
|
||||
context = new JsonTrackingContext(ioManager, customCommandHandler, loggerFactory.CreateLogger<JsonTrackingContext>(), () =>
|
||||
{
|
||||
lock (trackingContexts)
|
||||
trackingContexts.Remove(context);
|
||||
}, ioManager.ConcatPath(basePath, commandsJsonName), ioManager.ConcatPath(basePath, channelsJsonName));
|
||||
context = new JsonTrackingContext(
|
||||
ioManager,
|
||||
customCommandHandler,
|
||||
loggerFactory.CreateLogger<JsonTrackingContext>(),
|
||||
() =>
|
||||
{
|
||||
lock (trackingContexts)
|
||||
trackingContexts.Remove(context);
|
||||
},
|
||||
ioManager.ConcatPath(basePath, commandsJsonName),
|
||||
ioManager.ConcatPath(basePath, channelsJsonName));
|
||||
Task task;
|
||||
lock (trackingContexts)
|
||||
{
|
||||
@@ -559,6 +571,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (mappedChannels)
|
||||
task = context.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken);
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
return context;
|
||||
}
|
||||
@@ -598,7 +611,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion);
|
||||
List<ulong> wdChannels;
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
string HelpText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the command should only be available to <see cref="User"/>s who's <see cref="User.Channel"/> has <see cref="Channel.IsAdmin"/> set
|
||||
/// If the command should only be available to <see cref="User"/>s who's <see cref="User.Channel"/> has <see cref="Channel.IsAdminChannel"/> set
|
||||
/// </summary>
|
||||
bool AdminOnly { get; }
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// TODO: Decomplexify
|
||||
#pragma warning disable CA1506
|
||||
public async Task<string> Invoke(string arguments, User user, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<Models.TestMerge> results = null;
|
||||
@@ -73,6 +75,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
return "Repository unavailable!";
|
||||
head = repo.Head;
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
|
||||
.SelectMany(x => x.ActiveTestMerges)
|
||||
.Select(x => x.TestMerge)
|
||||
@@ -89,10 +92,8 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List<Models.TestMerge>();
|
||||
}
|
||||
|
||||
if (!results.Any())
|
||||
return "None!";
|
||||
|
||||
return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7))));
|
||||
return !results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7))));
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
/// <summary>
|
||||
/// Construct a <see cref="VersionCommand"/>
|
||||
/// </summary>
|
||||
/// <param name="application"></param>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
public VersionCommand(IApplication application)
|
||||
{
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
|
||||
@@ -38,6 +38,15 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
bool active;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="JsonTrackingContext"/>
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="customCommandHandler">The value of <see cref="customCommandHandler"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
|
||||
/// <param name="commandsPath">The value of <see cref="commandsPath"/></param>
|
||||
/// <param name="channelsPath">The value of <see cref="channelsPath"/></param>
|
||||
public JsonTrackingContext(IIOManager ioManager, ICustomCommandHandler customCommandHandler, ILogger<JsonTrackingContext> logger, Action onDispose, string commandsPath, string channelsPath)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
@@ -90,6 +99,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
logger.LogWarning("Error retrieving custom commands! Exception: {0}", e);
|
||||
}
|
||||
|
||||
return new List<CustomCommand>();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
/// <param name="fromDiscord">The mention <see cref="string"/> provided by the Discord library</param>
|
||||
/// <returns>The normalized mention <see cref="string"/></returns>
|
||||
static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", "", StringComparison.Ordinal);
|
||||
static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", String.Empty, StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DiscordProvider"/>
|
||||
@@ -97,10 +97,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Channel = new Channel
|
||||
{
|
||||
RealId = e.Channel.Id,
|
||||
IsPrivate = pm,
|
||||
IsPrivateChannel = pm,
|
||||
ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN",
|
||||
FriendlyName = e.Channel.Name
|
||||
//isAdmin and Tag populated by manager
|
||||
|
||||
// isAdmin and Tag populated by manager
|
||||
},
|
||||
FriendlyName = e.Author.Username,
|
||||
Mention = NormalizeMention(e.Author.Mention)
|
||||
@@ -182,19 +183,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (!channel.DiscordChannelId.HasValue)
|
||||
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
|
||||
|
||||
if (!(client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel))
|
||||
return null;
|
||||
if (client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel)
|
||||
return new Channel
|
||||
{
|
||||
RealId = discordChannel.Id,
|
||||
IsAdminChannel = channel.IsAdminChannel == true,
|
||||
ConnectionName = discordChannel.Guild.Name,
|
||||
FriendlyName = discordChannel.Name,
|
||||
IsPrivateChannel = false,
|
||||
Tag = channel.Tag
|
||||
};
|
||||
|
||||
return new Channel
|
||||
{
|
||||
RealId = discordChannel.Id,
|
||||
IsAdmin = channel.IsAdminChannel == true,
|
||||
ConnectionName = discordChannel.Guild.Name,
|
||||
FriendlyName = discordChannel.Name,
|
||||
IsPrivate = false,
|
||||
Tag = channel.Tag
|
||||
};
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null).ToList();
|
||||
|
||||
|
||||
@@ -44,18 +44,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// Address of the server to connect to
|
||||
/// </summary>
|
||||
readonly string address;
|
||||
|
||||
/// <summary>
|
||||
/// Port of the server to connect to
|
||||
/// </summary>
|
||||
readonly ushort port;
|
||||
|
||||
/// <summary>
|
||||
/// IRC nickname
|
||||
/// </summary>
|
||||
readonly string nickname;
|
||||
|
||||
/// <summary>
|
||||
/// Password which will used for authentication
|
||||
/// </summary>
|
||||
readonly string password;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IrcPasswordType"/> of <see cref="password"/>
|
||||
/// </summary>
|
||||
@@ -134,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
UseSsl = useSsl
|
||||
};
|
||||
if (useSsl)
|
||||
client.ValidateServerCertificate = true; //dunno if it defaults to that or what
|
||||
client.ValidateServerCertificate = true; // dunno if it defaults to that or what
|
||||
|
||||
client.OnChannelMessage += Client_OnChannelMessage;
|
||||
client.OnQueryMessage += Client_OnQueryMessage;
|
||||
@@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (Connected)
|
||||
{
|
||||
disconnecting = true;
|
||||
client.Disconnect(); //just closes the socket
|
||||
client.Disconnect(); // just closes the socket
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,8 +188,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (dicToCheck == queryChannelIdMap)
|
||||
channelIdMap.Add(resultId.Value, null);
|
||||
}
|
||||
|
||||
return resultId.Value;
|
||||
};
|
||||
}
|
||||
|
||||
ulong userId, channelId;
|
||||
lock (this)
|
||||
@@ -204,8 +209,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
ConnectionName = address,
|
||||
FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName,
|
||||
RealId = channelId,
|
||||
IsPrivate = isPrivate
|
||||
//isAdmin and Tag populated by manager
|
||||
IsPrivateChannel = isPrivate
|
||||
|
||||
// isAdmin and Tag populated by manager
|
||||
},
|
||||
FriendlyName = username,
|
||||
RealId = userId,
|
||||
@@ -247,9 +253,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
if (passwordType == IrcPasswordType.Sasl)
|
||||
{
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); //needs to be put in the buffer before anything else
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
client.Login(nickname, nickname, 0, nickname);
|
||||
}
|
||||
|
||||
@@ -260,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
else if (passwordType == IrcPasswordType.Sasl)
|
||||
{
|
||||
//wait for the sasl ack or timeout
|
||||
// wait for the sasl ack or timeout
|
||||
var recievedAck = false;
|
||||
var recievedPlus = false;
|
||||
client.OnReadLine += (sender, e) =>
|
||||
@@ -285,7 +292,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
for (; !recievedPlus && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult())
|
||||
client.Listen(false);
|
||||
|
||||
//Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
// Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
var authString = String.Format(CultureInfo.InvariantCulture, "{0}{1}{0}{1}{2}", nickname, '\0', password);
|
||||
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString));
|
||||
var authLine = String.Format(CultureInfo.InvariantCulture, "AUTHENTICATE {0}", b64);
|
||||
@@ -306,10 +313,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (disconnecting || !client.IsConnected)
|
||||
break;
|
||||
client.Listen(false);
|
||||
//ensure we have the correct nick
|
||||
|
||||
// ensure we have the correct nick
|
||||
if (client.GetIrcUser(nickname) == null)
|
||||
client.RfcNick(nickname);
|
||||
}
|
||||
|
||||
client.Listen();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
@@ -322,6 +331,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
logger.LogWarning("Unable to connect to IRC: {0}", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
@@ -336,7 +346,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
try
|
||||
{
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise it wont go through
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -363,7 +373,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
throw new InvalidOperationException("ChatChannel missing IrcChannel!");
|
||||
lock (this)
|
||||
{
|
||||
var hs = new HashSet<string>(); //for unique inserts
|
||||
var hs = new HashSet<string>(); // for unique inserts
|
||||
foreach (var I in channels)
|
||||
hs.Add(I.IrcChannel);
|
||||
var toPart = new List<string>();
|
||||
@@ -390,13 +400,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
id = channelIdCounter++;
|
||||
channelIdMap.Add(id.Value, x.IrcChannel);
|
||||
}
|
||||
|
||||
return new Channel
|
||||
{
|
||||
RealId = id.Value,
|
||||
IsAdmin = x.IsAdminChannel == true,
|
||||
IsAdminChannel = x.IsAdminChannel == true,
|
||||
ConnectionName = address,
|
||||
FriendlyName = channelIdMap[id.Value],
|
||||
IsPrivate = false,
|
||||
IsPrivateChannel = false,
|
||||
Tag = x.Tag
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
@@ -33,10 +33,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
@@ -46,6 +48,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// The <see cref="Api.Models.Instance"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly Api.Models.Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CancellationTokenSource"/> for <see cref="cleanupTask"/>
|
||||
/// </summary>
|
||||
@@ -55,10 +58,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <see cref="Task"/> representing calls to <see cref="CleanJob(CompileJob)"/>
|
||||
/// </summary>
|
||||
Task cleanupTask;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TaskCompletionSource{TResult}"/> resulting in the latest <see cref="DmbProvider"/> yet to exist
|
||||
/// </summary>
|
||||
TaskCompletionSource<object> newerDmbTcs;
|
||||
|
||||
/// <summary>
|
||||
/// The latest <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
@@ -87,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => cleanupCts.Dispose(); //we don't dispose nextDmbProvider here, since it might be the only thing we have
|
||||
public void Dispose() => cleanupCts.Dispose(); // we don't dispose nextDmbProvider here, since it might be the only thing we have
|
||||
|
||||
/// <summary>
|
||||
/// Delete the <see cref="Api.Models.Internal.CompileJob.DirectoryName"/> of <paramref name="job"/>
|
||||
@@ -99,12 +104,13 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
{
|
||||
var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token);
|
||||
Task otherTask;
|
||||
//lock (this) //already locked below
|
||||
|
||||
// lock (this) //already locked below
|
||||
otherTask = cleanupTask;
|
||||
await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (!jobLockCounts.TryGetValue(job.Id, out var currentVal) || currentVal == 1)
|
||||
{
|
||||
jobLockCounts.Remove(job.Id);
|
||||
@@ -116,7 +122,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var decremented = --jobLockCounts[job.Id];
|
||||
logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -156,13 +161,14 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
|
||||
{
|
||||
//where complete clause not necessary, only successful COMPILEjobs get in the db
|
||||
// where complete clause not necessary, only successful COMPILEjobs get in the db
|
||||
var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id)
|
||||
.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (cj == default(CompileJob))
|
||||
return;
|
||||
await LoadCompileJob(cj, cancellationToken).ConfigureAwait(false);
|
||||
//we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet
|
||||
|
||||
// we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -173,17 +179,18 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
public async Task<IDmbProvider> FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
|
||||
{
|
||||
if (compileJob == null)
|
||||
throw new ArgumentNullException(nameof(compileJob));
|
||||
|
||||
//ensure we have the entire compile job tree
|
||||
// ensure we have the entire compile job tree
|
||||
await databaseContextFactory.UseContext(async db => compileJob = await db.CompileJobs.Where(x => x.Id == compileJob.Id)
|
||||
.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)
|
||||
.FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query
|
||||
.FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); // can't wait to see that query
|
||||
|
||||
logger.LogTrace("Loading compile job {0}...", compileJob.Id);
|
||||
var providerSubmitted = false;
|
||||
@@ -201,7 +208,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
if (!(await primaryCheckTask.ConfigureAwait(false) && await secondaryCheckTask.ConfigureAwait(false)))
|
||||
{
|
||||
logger.LogWarning("Error loading compile job, .dmb missing!");
|
||||
return null; //omae wa mou shinderu
|
||||
return null; // omae wa mou shinderu
|
||||
}
|
||||
|
||||
lock (this)
|
||||
@@ -226,28 +233,31 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
newProvider.Dispose();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
public async Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken)
|
||||
{
|
||||
List<long> jobIdsToSkip;
|
||||
//don't clean locked directories
|
||||
|
||||
// don't clean locked directories
|
||||
lock (this)
|
||||
jobIdsToSkip = jobLockCounts.Select(x => x.Key).ToList();
|
||||
|
||||
List<string> jobUidsToNotErase = null;
|
||||
|
||||
//find the uids of locked directories
|
||||
// find the uids of locked directories
|
||||
await databaseContextFactory.UseContext(async db =>
|
||||
{
|
||||
jobUidsToNotErase = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id)).Select(x => x.DirectoryName.Value.ToString().ToUpperInvariant()).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
//add the other exemption
|
||||
// add the other exemption
|
||||
if (exceptThisOne != null)
|
||||
jobUidsToNotErase.Add(exceptThisOne.DirectoryName.Value.ToString().ToUpperInvariant());
|
||||
|
||||
//cleanup
|
||||
// cleanup
|
||||
await ioManager.CreateDirectory(".", cancellationToken).ConfigureAwait(false);
|
||||
var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false);
|
||||
int deleting = 0;
|
||||
@@ -276,5 +286,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
await Task.WhenAll().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <inheritdoc />
|
||||
public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
|
||||
|
||||
/// <inheritdoc />
|
||||
public RevisionInformation RevisionInformation => CompileJob.RevisionInformation;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/> for the <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
@@ -28,6 +25,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Action"/> to run when <see cref="Dispose"/> is called
|
||||
/// </summary>
|
||||
|
||||
@@ -24,14 +24,17 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// Name of the primary directory used for compilation
|
||||
/// </summary>
|
||||
public const string ADirectoryName = "A";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the secondary directory used for compilation
|
||||
/// </summary>
|
||||
public const string BDirectoryName = "B";
|
||||
|
||||
/// <summary>
|
||||
/// Extension for .dmbs
|
||||
/// </summary>
|
||||
public const string DmbExtension = ".dmb";
|
||||
|
||||
/// <summary>
|
||||
/// Extension for .dmes
|
||||
/// </summary>
|
||||
@@ -41,42 +44,52 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// The <see cref="IByondManager"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IByondManager byond;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="StaticFiles.IConfiguration"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly StaticFiles.IConfiguration configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISessionControllerFactory"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly ISessionControllerFactory sessionControllerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICompileJobConsumer"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly ICompileJobConsumer compileJobConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApplication"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IApplication application;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IEventConsumer"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IChat"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IChat chat;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessExecutor"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IProcessExecutor processExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IWatchdog watchdog;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
@@ -133,13 +146,13 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
{
|
||||
AllowWebClient = false,
|
||||
PrimaryPort = portToUse,
|
||||
SecurityLevel = securityLevel, //all it needs to read the file and exit
|
||||
SecurityLevel = securityLevel,
|
||||
StartupTimeout = timeout
|
||||
};
|
||||
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
|
||||
job.MinimumSecurityLevel = securityLevel; //needed for the TempDmbProvider
|
||||
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
|
||||
var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job);
|
||||
|
||||
var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);
|
||||
@@ -184,13 +197,13 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new JobException("DMAPI validation timed out!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a .dme with DreamMaker
|
||||
/// Compiles a .dme with DreamMaker
|
||||
/// </summary>
|
||||
/// <param name="dreamMakerPath">The path to the DreamMaker executable</param>
|
||||
/// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param>
|
||||
@@ -267,6 +280,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 //TODO: Decomplexify
|
||||
public async Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, Action<int> progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
|
||||
{
|
||||
if (revisionInformation == null)
|
||||
@@ -327,8 +341,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var progressTask = ProgressTask();
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
|
||||
string remoteCommitInsert;
|
||||
if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
|
||||
@@ -366,7 +378,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
|
||||
await chatTask.ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -375,18 +387,18 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName);
|
||||
|
||||
// copy the repository
|
||||
logger.LogTrace("Copying repository to game directory...");
|
||||
//copy the repository
|
||||
var fullDirA = ioManager.ResolvePath(dirA);
|
||||
var repoOrigin = repository.Origin;
|
||||
using (repository)
|
||||
await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//run precompile scripts
|
||||
// run precompile scripts
|
||||
var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName));
|
||||
await eventConsumer.HandleEvent(EventType.CompileStart, new List<string> { resolvedGameDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//determine the dme
|
||||
// determine the dme
|
||||
if (job.DmeName == null)
|
||||
{
|
||||
logger.LogTrace("Searching for available .dmes...");
|
||||
@@ -403,11 +415,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
|
||||
await ModifyDme(job, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//run compiler, verify api
|
||||
// run compiler
|
||||
job.ByondVersion = byondLock.Version.ToString();
|
||||
|
||||
var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// verify api
|
||||
try
|
||||
{
|
||||
if (exitCode != 0)
|
||||
@@ -417,7 +430,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
catch (JobException)
|
||||
{
|
||||
//server never validated or compile failed
|
||||
// DD never validated or compile failed
|
||||
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -427,12 +440,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
|
||||
logger.LogTrace("Duplicating compiled game...");
|
||||
|
||||
//duplicate the dmb et al
|
||||
// duplicate the dmb et al
|
||||
await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogTrace("Applying static game file symlinks...");
|
||||
|
||||
//symlink in the static data
|
||||
// symlink in the static data
|
||||
var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
|
||||
var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken);
|
||||
|
||||
@@ -463,5 +476,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,5 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KeepAlive() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components
|
||||
readonly IConfiguration configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="EventConsumer"/>
|
||||
/// </summary>
|
||||
IWatchdog watchdog;
|
||||
|
||||
@@ -46,8 +46,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
public void SetWatchdog(IWatchdog watchdog)
|
||||
{
|
||||
#pragma warning disable IDE0016 // Use 'throw' expression
|
||||
if (watchdog == null)
|
||||
throw new ArgumentNullException(nameof(watchdog));
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
if (this.watchdog != null)
|
||||
throw new InvalidOperationException("watchdog already set!");
|
||||
this.watchdog = watchdog;
|
||||
|
||||
@@ -9,18 +9,22 @@
|
||||
/// Parameters: Reference name, commit sha
|
||||
/// </summary>
|
||||
RepoResetOrigin = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Checkout target
|
||||
/// </summary>
|
||||
RepoCheckout = 1,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// </summary>
|
||||
RepoFetch = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Pull request number, pull request sha, merger message
|
||||
/// </summary>
|
||||
RepoMergePullRequest = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Absolute path to repository root
|
||||
/// </summary>
|
||||
@@ -30,26 +34,32 @@
|
||||
/// Parameters: Version being installed
|
||||
/// </summary>
|
||||
ByondInstallStart = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Error string
|
||||
/// </summary>
|
||||
ByondInstallFail = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Old active version, new active version
|
||||
/// </summary>
|
||||
ByondActiveVersionChange = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, origin commit sha
|
||||
/// </summary>
|
||||
CompileStart = 8,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// </summary>
|
||||
CompileCancelled = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise
|
||||
/// </summary>
|
||||
CompileFailure = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path
|
||||
/// </summary>
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// The <see cref="IByondManager"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IByondManager ByondManager { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IChat Chat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="StaticFiles.IConfiguration"/> for the <see cref="IInstance"/>
|
||||
/// The <see cref="IConfiguration"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IConfiguration Configuration { get; }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user